This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

This article is published in The depth of WebGL, click to see the table of contents

In this article we are going to relax and play with Canvas 2D to achieve some interesting effects. You can try them together.

Color the canvas

Let’s start with HTML:


<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>canvas 2d</title>
    <style>* {margin: 0;
            padding: 0;
        }
        canvas {
            width: 100vw;
            height: 100vh
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
</body>
<script src="./index.js"></script>
</html>
Copy the code

Canvas pixel manipulation

In Canvas 2D, you can rely on ImageData for pixel manipulation. We can manipulate pixels with getImageData(), putImageData(), and createImageData().

CreateImageData () and getImageData() yield an ImageData object whose data is a Uint8ClampedArray class array. We also have width and height.

It represents an image whose width is width and height is height, where the color of each pixel is defined in data, arranged in the Uint8ClampedArray from left to right and from top to bottom. Groups of 4 represent RGBA.

For example, the first four terms are [255,0,0,255]. That means the first pixel is red. So if we use createImageData() to create an empty ImageData that is the size of the Canvas. Then modify the data value inside, and finally draw putImageData() to canvas. We can doodle every pixel on the canvas! In principle we could do a lot of work! Let’s try painting the entire canvas red first!

const canvas = document.getElementById('canvas')

function resizeCanvasSize(){
  canvas.width = canvas.clientWidth * window.devicePixelRatio
  canvas.height = canvas.clientHeight * window.devicePixelRatio
}

resizeCanvasSize()

const ctx = canvas.getContext('2d')
const imageData = ctx.createImageData(canvas.width, canvas.height)


function draw(x, y){
  return {
    x: 255.y: 0.z: 0.w: 255}}for( let i = 0; i < imageData.width; ++i ) {
    for( let j = 0; j < imageData.height; ++j ){
        const index = imageData.width * j * 4 + i * 4;
        
        const color = draw(i, j);

        imageData.data[index] = color.x;
        imageData.data[index+1] = color.y;
        imageData.data[index+2] =  color.z;
        imageData.data[index+3] = color.w;
    }
}

ctx.putImageData(imageData, 0.0.0.0 ,canvas.width, canvas.height)

Copy the code

We are now able to color every pixel of the canvas. Now let’s try some flower work!


Let me draw a triangle

How do we take advantage of our ability to color every pixel to draw a triangle? First we make it clear that we need to provide the program with three points as the vertices of the triangle. Then we walk through each pixel to determine whether the triangle is inside the triangle.

We can tell if a point is in a triangle by using the cross product. The diagram below:

The blue vectors that we see are the vertices subtracted from the triangle. And then we take each of the three vectors cross p minus the starting points of each of the three vectors. Such as:

We set:


x = A ~ C ~ \vec x = \tilde A – \tilde C


y = B ~ A ~ \vec y = \tilde B – \tilde A


z = C ~ B ~ \vec z = \tilde C – \tilde B


q = P ~ C ~ \vec q = \tilde P – \tilde C


w = P ~ A ~ \vec w = \tilde P – \tilde A


e = P ~ B ~ \vec e = \tilde P – \tilde B

We calculate x⃗×q⃗ \vec x × \vec qx ×q y⃗× W ⃗\vec y × \vec wy ×w z⃗×e e e ez ×e respectively. And then determine if the cross product is in the same direction. (The two-dimensional equivalent of z is 0, just add 0). So we know whether each point is inside or outside the triangle.

If you are interested in the code, you can try to implement it yourself

Barycentric coordinates

We’re going to go up and see if each pixel is inside the triangle, and then what we’re going to do is not only do that, but we’re going to figure out where this point is in the triangle. For example, we will draw the following result:

Observe the positions of each vertex as red, green, and blue. The rest of the triangle takes the red ratio, the blue ratio, and the green ratio depending on how far they are from the three vertices.

This is a very useful thing to do, as interpolation on various images in WebGL is done this way. This is interpolation based on the color of the vertices. Later can also interpolate normal, image texture UV. Because the thing about displaying things (rasterization) is to determine the color of each pixel. But our input is only the vertex and the information attached to the vertex, so the other points need to be inferred by the program. This process is called interpolation. Interpolation can also be used in motion. For example, an object determines where to start and where to end. So the states in between have to be interpolated. Some video editing software defines key frames, and the process of automatically generating frames in the middle is also interpolation.

How do you compute the coordinates of the center of mass?

In the figure above, we say that the barycentric coordinates of a point (also called area coordinates in triangles) are the ratios of the three smaller triangles to the larger triangle. The ratio of each triangle to the larger triangle determines the ratio of this point to the opposite vertex. Such as:

△ABP/△ABC\triangle ABP/ \triangle ABC△ABP/△ABC is the ratio of point P to point C.

When we look at this ratio of triangles, they all have the same base. For example, the triangles above all have a common base AB. So the triangle ratio is actually the distance from P to AB over the distance from C to AB. (The distance from dot to straight line, which I’m sure you knew all about in high school).

Let the equation of the line be Ax+By+C=0, and the coordinate of point P is (x0,y0), then the distance between P and the line is:

First, according to P1, P2, use two points (should be middle school) to find the realization equation. And then it turns out that the ratio of the distance between the two points and the straight line is ok let’s just get rid of the square root of the bottom. So the code for the calculated result should look like this:

Skipping a course of junior high math…


    const u = ((P2.y - P3.y) * P.x + (P3.x - P2.x) * P.y + (P2.x * P3.y - P3.x * P2.y)) / ((P2.y - P3.y) * P1.x + (P3.x - P2.x) * P1.y + (P2.x * P3.y - P3.x * P2.y)); 
    const v = ((P1.y - P3.y) * P.x + (P3.x - P1.x) * P.y + (P1.x * P3.y - P3.x * P1.y)) / ((P1.y - P3.y) * P2.x + (P3.x - P1.x) * P2.y + (P1.x * P3.y - P3.x * P1.y));
    const w = 1 - u - v;

Copy the code

Complete code implementation:


const canvas = document.getElementById('canvas')

function resizeCanvasSize(){
  canvas.width = canvas.clientWidth * window.devicePixelRatio;
  canvas.height = canvas.clientHeight * window.devicePixelRatio;
}

resizeCanvasSize()

const ctx = canvas.getContext('2d')

const imageData = ctx.createImageData(canvas.width, canvas.height)

const vertex = [
    400.100.1200.100.800.600,]function draw(x,y){
 
    const P = {x, y}
    const P1 = {x: vertex[0].y: vertex[1]}
    const P2 = {x: vertex[2].y: vertex[3]}
    const P3 = {x: vertex[4].y: vertex[5]}

    const u = ((P2.y - P3.y) * P.x + (P3.x - P2.x) * P.y + (P2.x * P3.y - P3.x * P2.y)) / ((P2.y - P3.y) * P1.x + (P3.x - P2.x) * P1.y + (P2.x * P3.y - P3.x * P2.y)); 
    const v = ((P1.y - P3.y) * P.x + (P3.x - P1.x) * P.y + (P1.x * P3.y - P3.x * P1.y)) / ((P1.y - P3.y) * P2.x + (P3.x - P1.x) * P2.y + (P1.x * P3.y - P3.x * P1.y));
    const w = 1 - u - v;
    
    if(u > 0 && u< 1 && v> 0 && v< 1 && w> 0 && w< 1) {
        return {
            x: 255 * u,
            y: 255 * v,
            z: 255 * w,
            w: 255,}}return {
        x: 0.y: 0.z: 0.w: 255,}}for( let i = 0; i < imageData.width; ++i ) {
    for( let j = 0; j < imageData.height; ++j ){
        const index = imageData.width * j * 4 + i * 4;
        
        const color = draw(i, j);

        imageData.data[index] = color.x;
        imageData.data[index+1] = color.y;
        imageData.data[index+2] =  color.z;
        imageData.data[index+3] = color.w; }}console.log(imageData)
ctx.putImageData(imageData, 0.0.0.0 ,canvas.width, canvas.height)
Copy the code

Computing the coordinates of the center of gravity is also fundamental to graphics. Network water is not deep, anyone can grasp. There are many, many articles on this topic. It’s easy and you’re familiar with this calculation. The simple usage of Canvas 2D in this chapter will also be used in future articles. The power of this Canvas2D will get better and better in your hands. New one!