In 2d graphics visualization development, it is often necessary to draw the selected effect of the object. In general, expression object selection can use border, outline, or glow effects. The effect of luminescence can be achieved by using the shadow function of canvas, which is relatively easy to achieve and will not be described here.

Draw the border

Drawing borders is the easiest effect to achieve, as shown in the image below

To draw borders, just use strokeRect. The effect is shown below:

This code is also very simple, as follows:

ctx1.strokeStyle = "red"; ctx1.lineWidth = 2; StrokeRect (1,1,img.width,img.height);Copy the code

Outline drawing

The problem is, simply adding a border doesn’t cut it. Most of the time, what people want is the silhouette effect, which is the image with and without pixels at the edges. The following image looks like this:

The easiest way to do this is to determine the edges by counting pixels and fill them with pixels of a specific color. However, the pixel calculation algorithm is not easy, and the simple algorithm is difficult to achieve the desired effect, and because of the per-pixel operation, the efficiency is not high.

Considering that in 3D WebGL, the algorithm idea for contour calculation is as follows:

  1. First draw the 3D model itself, and start the template test during drawing, and save the 3D image to the template buffer.
  2. Enlarge the model properly, use pure draw model, and enable template test when drawing, compare with the previous template buffer pixel, if the corresponding coordinate in the previous template buffer pixel, do not draw solid color.

According to the above principle, you can draw the outline of the 3d object. Here is a sample effect, (seeStemkoski. Making. IO/Three js/Ou…)

There is a similar principle for contour effects in 2D canvas using the globalCompositeOperation. The general idea is this:

  1. First draw a larger image.
  2. Then enable globalCompositeOperation = ‘source-in’ and fill the entire Canvas area with a solid color. Due to the source-in effect, the solid color will fill the area of the enlarged image with pixels.
  3. Use the default globalCompositeOperation (source-over) to draw images at their original size.

Draw larger images

You can control the size of the image to be drawn by using the drawImage parameter, as shown below. DrawImage has several forms:

1  void ctx.drawImage(image, dx, dy);
2  void ctx.drawImage(image, dx, dy, dWidth, dHeight);
3  void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Copy the code

Where dx and dy represent the starting position of drawing. Generally, the first method is used when drawing, which means that the size of drawing is the size of the original picture. Using the second method, we can specify the size to draw. We can use the second method to draw an enlarged image, as shown in the code:

ctx.drawImage(img, p - s, p  - s, w + 2 * s, h+ 2 * s);
Copy the code

Where p represents the drawing position of the picture itself, s represents the left and upward offset, and the width and height of the picture are increased by 2 * s

Fill the area of the enlarged image with a solid color

Based on the previous drawing, open globalCompositeOperation = ‘source-in’ and fill the entire Canvas area with a solid color. The code looks like this:

 // fill with color
        ctx.globalCompositeOperation = "source-in";
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(0, 0, cw, ch);
Copy the code

The final result is shown below:

The reason for this effect is the use of globalCompositeOperation = ‘source-in’, as described in my other articles.

Draw the original image

The last step is to draw the original image as follows:

CTX. GlobalCompositeOperation = "source - over"; ctx.drawImage(img, p, p, w, h);Copy the code

First restore globalCompositeOperation to the default “source-over” and then draw the image as it should be.

After the above steps, the final effect is shown in the picture below:

And you can see that we finally got what we were looking for.

Show only the outline

If we only want the outline of the image, we can set the globalCompositeOperation to “destination-out” at the end of the drawing as follows:

        ctx.globalCompositeOperation = "destination-out";
        ctx.drawImage(img, p, p, w, h);
Copy the code

The renderings are as follows:

The problem of inconsistent outline thickness

The above algorithm realizes that the center of the region with pixel value of the image is basically the same as the geometric center of the image itself. If the center with pixel value of the image is quite different from the geometric center of the image itself, there will be inconsistent contour thickness, such as the following picture:

The top half is transparent, the bottom half is opaque, the center of the pixel is 3/4 out, and the geometric center is 1/2 out. Using the algorithm above, the outline of the image looks like this:

You can see that the width of the top edge is 0.

In the picture below, for example,

After drawing, the outline of the upper edge is thinner than that of the other edges.

How do you deal with that? You can draw enlarged pictures, not directly using zoom, but up and down around, up and down left, up and right, down left, down right several directions for offset drawing, drawing many times, the code is as follows:

  var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array
 // draw images at offsets from the array scaled by s
 for (var i = 0; i < dArr.length; i += 2) {
     ctx.drawImage(img, p + dArr[i] * s, p + dArr[i + 1] * s, w, h);
  }
Copy the code

Look again at the outline effect of the image above, as follows:

Translucency

As I mentioned in other articles, when the globalCompositeOperation is “source-in”, the transparency of the source graphics affects the transparency of the target graphics. This will result in the pixel value of the contour being multiplied by transparency. For example, we set globalAlpha = 0.5 to simulate the magnified image. The final drawing looks like this:

You can see that the outline is getting lighter. The solution is to zoom in a few more times. Such as:

CTX. GlobalAlpha = 0.5; ctx.drawImage(img, p - s, p - s, w + 2 * s, h+ 2 * s); ctx.drawImage(img, p - s, p - s, w + 2 * s, h+ 2 * s);Copy the code

However, when the above method is drawn by offset, it has been drawn many times, so there is no such problem. As follows:

CTX. GlobalAlpha = 0.5; for (var i = 0; i < dArr.length; i += 2) { ctx.drawImage(img, p + dArr[i] * s, p + dArr[i + 1] * s, w, h); }Copy the code

As shown below:

Of course, drawing many times is not a good solution when transparency is low.

Marching -squares-algorithm doesn’t work well with some images, like this one:

Since it has a lot of hollow effects, the final effect looks like this:

But what you want is just the outer contour, and you don’t need the hollow part to draw the contour effect. In this case, you need to use other algorithms. Use the Marching Squares algorithm directly to get the edges of an image. The specific implementation of this algorithm will not be explained in this paper, but will be explained in a separate article later. The open source implementation is used directly here. For example, you can use github.com/sakri/March… , the code is as follows:

function drawOuttline2(){ var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var w = img.width; var h = img.height; canvas.width = w; canvas.height = h; ctx.drawImage(img, 0, 0, w, h); var pathPoints = MarchingSquares.getBlobOutlinePoints(canvas); var points = []; for(var i = 0; i < pathPoints.length; i += 2){ points.push({ x:pathPoints[i], y:pathPoints[i + 1], }) } // ctx.clearRect(0, 0, w, h); ctx.beginPath(); ctx.lineWidth = 2; ctx.strokeStyle = '#00CCFF'; ctx.moveTo(points[0].x, points[0].y); for (var i = 1; i < points.length; i += 1) { var point = points[i]; ctx.lineTo(point.x,point.y); } ctx.closePath(); ctx.stroke(); Ctx1. DrawImage (canvas, 0, 0); }Copy the code

First get the img image’s set of contour points using the method called MarchingSquaresJS, and then join all the points together. The outline is formed, and the final effect is as follows:

However, it can be seen that the MarchingSquares algorithm gets a relatively jagged silhouette effect. The optimization of the light-block algorithm is not explained in this paper.

conclusion

For images with no hollowing effects, we generally eschewed MarchingSquares in favor of the former method, which is more efficient and relatively more effective. For hollow squares, MarchingSquares is less effective and less efficient, allowing caching to reduce performance loss in practice.

The origin of this article is a 2.5D project.

Reference documentation

www.emanueleferonato.com/2013/03/01/… Github.com/sakri/March… Github.com/OSUblake/ms… Users. Polytech. Unice. Fr / ~ lingrand/M…

If you are interested in visualization, follow the public account “ITMan” to receive more valuable articles in time. You can also add wechat 541002349 for communication.