Canvas2d drawing step pits

Demand style

The requirements look like this:

The size of canvas drawn is required to be 370*70 (the top and bottom are divided into two canvas, one of which is 370*70)

The existing material is that the UI gives images of small figures of different sizes and colors, including gray and blue male figures, and gray and red female figures.

The sizes are 22*59, 44*108 and 66*167

Problem analysis

First determine the composition elements for the UI to provide the picture, and draw text.

The API for drawing text is relatively simple, basically no pit, no repetitions.

By checking the CANVAS API, we can know:

  • void drawImage(image,dx,dy);Image supports many types, includingHTMLImageElement.SVGImageElementWait, I’m going to use it hereHTMLImageElement. Dx and dy refer to the starting coordinates drawn on the canvas during image drawing. However, two parameters are not enough, so use the following two apis when drawing.
  • void drawImage(image,dx,dy,dw,dh);In the same form above, this specifies the width and height of the image to draw on the canvas, passed in by dw and DH.
  • void drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh);This is the most complex, the most flexible forms of use, the first parameter is for drawing elements, the second to the fifth parameter, specify the coordinates of the original image and width is high, this area will be drawn to the canvas, and other area is ignored, the last four parameters like form 2, specifies the canvas target coordinates in the high and wide.

Diagram of API on MDN

As you can see, you can draw the entire person just by using the second 4-parameter API.

The problem is how to draw a half-grey, half-colored little man. Using the third 8-parameter API, you can capture a portion of the gray figure image and overlay it with a full colored figure already drawn on the canvas. This will create a half-grey, half-colored figure.

Began to draw

Problem 1: Canvas image loading

Canvas can be drawn only after the picture is loaded, that is, the load event of the picture needs to be monitored. How to make sure that all images are loaded before calling the Canvas drawing API when there are multiple images (for example, there are 4 different figures)?

You can create a picDetail object

let PicDetail={count:0}
Copy the code

Pass it to the preLoad function, and when img.src is assigned, make count+1 when the callback is triggered. ReCallback returns a callback function. Each time the callback is triggered, count– is called, and count is 0. When this is 0, all images are loaded and the drawing is ready.

function PreLoad(ctx,picDetail,dpr){
  // Create img1 element. picDetail.count++ ...// Add an event listener to img1
  Img1.addEventListener(
    'load',reCallback(ctx,picDetail,dpr),false
  );
  // Create the img2 element. picDetail.count++ ...// Add an event listener to img2
  Img2.addEventListener(
    'load',reCallback(ctx,picDetail,dpr),false
);
Copy the code

ReCallback function

function reCallback(ctx,picDetail,dpr){
  return (a)= >{
    picDetail.count--
    if(picDetail.count! = =0) {return 0 // The callback terminates, indicating that an image has not been loaded
    } else{
      // Start drawing}}}Copy the code

If count+1 is used, then count+1 is used, then count+1 is used, then count+1 is used.

This should not be a problem because count++ is done in a js section, and the browser will start loading img only after the js execution is complete. After the load is complete, the callback is added to the task queue to wait for the event loop to execute.

Problem 2: Canvas picture is blurred

After solving the image loading problem, I can enter the drawing code section, but I find that drawing images is always very blurred, why is this? On the one hand, CSS scaling, on the other hand, DPR, and on the other hand, Canvas API scaling.

We solve them one by one

Remember that the UI provides images in three sizes? They are 22*59, 44*108 and 66*167 respectively. According to requirements, the canvas size of one gender is 370*70.

This means that the actual CSS size of the image will only be 22*59, so we start with dWidth and dHeight values of 22 and 59.

Img is naturally the bigger the better, as the original image has a higher resolution and is clearer, which makes sense. Therefore, 66*167 is selected as image, while the values of dWidth and dHeight are 22 and 59.

ctx.drawImage(image66 * 167 ` `, dx, dy, dWidth` 22 `, dHeight59 ` `);
Copy the code

The resulting image is not clear, the problem may be the image reduction, should not use API to force the image reduction. As a result, the canvas itself has only 22 and 59 pixels

The logical solution is to use CSS with canvas for scaling. Canvas can be drawn very large. The width and height of canvas determines how many pixels it has, while the width and height of CSS of canvas determines the size of canvas display. As long as the display size meets the requirements, different pictures are used for canvas according to different DPR. To draw canvas of different sizes, and finally into a fixed CSS size.

First, obtain the DPR of the current device. Since there are only images that meet DPR =1, 2, and 3, the DPR can only be 1, 2, and 3 according to the VALUE of DPR, which becomes the scale value.

// DPR initialization
let dpr;
if(window && window.devicePixelRatio){
  if(Object.prototype.toString.call(window.devicePixelRatio)==='[object Number]'){
    dpr = window.devicePixelRatio
  } else{
    dpr = 1}}else{
  dpr = 1 
}

// DPR integer, the actual scale value
if(dpr>0 && dpr <=1){
  dpr = 1 
} else if(dpr>1 && dpr <=2){
  dpr = 2
} else if(dpr>2 && dpr <=3){
  dpr = 3
} else {
  dpr = 3
}
Copy the code

Start scaling the canvas size:

// Canvas initializes
const manCanvas = document.getElementById('manCanvas');
// Where height and width refer to the width and height of the canvas pixel, not the CSS display size
manCanvas.height = 70 * dpr
manCanvas.width = 400 * dpr
const manCtx = manCanvas.getContext('2d');
Copy the code
<canvas id="manCanvas" class="canvasBox"></canvas>
Copy the code
.canvasBox{
  height: 70px;
  width: 400px;
}
Copy the code

In the same way, you can choose to use different images based on DPR

womanRedImg.src = dprToPic(dpr,'woman'.'red');Copy the code

As you can see, the drawing of the image is also based on DPR

ctx.drawImage(picDetail.manGrayImg,(82+31*i)*dpr,5*dpr,22*dpr,59*dpr);
Copy the code

The size of picdetail. manGrayImg is (22* DPR) * (59* DPR), avoiding the need to compress images through APIS.

After testing, DPR =1 on the PC, DPR =2 on the MAC screen can be clearly displayed.

Other problems: Inaccurate JS numerical calculation

Nothing to do with Canvas, but something to do with JS features

The back end is just going to return the number of men and women, and the front end is going to calculate the percentage, and according to the previous theory, you have to know what percentage of the little people, the gray people, are. However, js floating-point numbers are inaccurate, such as 0.7/0.1=6.999, so using math.floor () to calculate the proportion of grey in the noise figures will be wrong.

The solution is to make it all integers, because 7000/1000=7 is relatively accurate

Draw overlapping mousy figures

The code first draws a blue figure, then uses manPercent to determine the width of the gray figure, and finally overwrites it.

ctx.drawImage(picDetail.manBlueImg,(82+31*i)*dpr,5*dpr,22*dpr,59*dpr);

ctx.drawImage(picDetail.manGrayImg,0.0.22*dpr*manPercent,59*dpr,(82+31*i)*dpr,5*dpr,22*dpr*manPercent,59*dpr);
Copy the code

You’re done

After solving all the above problems, we finally realized the required interface. Canvas usually has little contact with each other, but we really learned a lot from this implementation, which is hereby recorded.