Basic concept

As we all know, a picture is made up of pixels. Each pixel contains four values that determine the rendered state. These four values are RGBA (Red, green, Blue, alpha).

The first three values are red, green and blue and range in size from 0 to 255, or from 0% to 100%.

The fourth value, alpha, specifies the transparency of the color, which ranges from 0 to 1. Where 0 represents full transparency and 1 represents full visibility.

Red green blue is the ternary color in colour, through setting the proportion that these 3 kinds of color place occupy, can irregular change gives other all colors.

For example, if you want to set the text of a label to red, you can set the RGBA value using CSS (code below).

span {
  color: rgba(255, 0, 0, 1);
}
Copy the code

Since each pixel can be represented by rGBA values, all the pixels contained in an image can be converted to data. If you change the RGBA value of a part of the pixels, the image will be rendered in a different way, so that the image can be edited.

So how do you turn images into data made up of pixels?

implementation

Picture conversion data

A simple HTML structure is as follows. A raw image and a canvas are placed on the page, each 300 in width and height.

<body>
  <p class="image">
     <img src="./img/rect.png" width="300" height="300" />
  </p>
  <canvas id="myCanvas" width="300" height="300"></canvas>
<body>
Copy the code

Start by writing a getImageData function to convert the original image into data (code below).

The following two steps are used to convert images into pixel data.

  • callctx.drawImageDraw the picture onto the canvas
  • callctx.getImageDataGet pixel data
const dom = document.getElementById("myCanvas"); // canvas getImageData(dom,"./img/rect.png"). Then ((data)=>{console.log(data); }) function getImageData(dom,url){const CTX = dom.getContext("2d"); // Set the environment to draw on the canvas const image = new image (); image.src= url; // Get the width of the canvas const w = dom.width; const h = dom.height; return new Promise((resolve)=>{ image.onload = function(){ ctx.drawImage(image, 0, 0 ,w,h); Const imgData = ctx.getimagedata (0,0,w,h); // draw the image on the canvas const imgData = ctx.getimagedata (0,0,w,h); Resolve (imgdata.data) // the data is a one-dimensional array, containing the RGBA data of the image ctx.clearrect (0,0,w,h); }})}Copy the code

The final printed data is as follows:

data = [255, 255, 255, 255, 255, 61, 61, 255, 255, 0, 0, 255, 255,...]
Copy the code

Data is a one-dimensional array. The first four values of the array [255, 255, 255, 255] are the RGBA values of the first pixel of the image (the transparency returned by CTx. getImageData ranges from 0 to 255),[255, 61, 61, 255] is the RGBA value of the second pixel of the image, and so on. This successfully converts images into data.

Data formatting

Although images were successfully converted into data, such data structures were difficult to manipulate, and we wanted to be able to keep the representation of data structures consistent with the image presentation.

Suppose there are four black pixels (as shown below), with a total width and height of 2 and a value of [0, 0, 0, 255,0, 0, 0, 255,0, 0, 0, 255].

Through a function transformation, the data becomes the following format.

[[[0, 0, 0, 255], [[0, 0, 0, 255]]], / / the first line of the [[0, 0, 0, 255], [[0, 0, 0, 255]]]] / / the second lineCopy the code

The above data format is consistent with the display structure of the image, and it is clear how many rows there are in the current image, how many pixels there are in each row, and the RGBA value of each pixel.

Given the above description, you can write a function normalize(code below) to convert data formats.

const dom = document.getElementById("myCanvas"); / / canvas, canvas getImageData (dom, ". / img/the rect. PNG "), then ((data) = > {the console. The log (the normalize (data, dom. Width, dom. Height)); }) function normalize(data,width,height){const list = []; const result = []; const len = Math.ceil(data.length/4); For (I = 0; i<len; i++){ const start = i*4; list.push([data[start],data[start+1],data[start+2],data[start+3]]); } for(hh = 0; hh < height; hh++){ const tmp = []; for(ww = 0; ww < width; ww++){ tmp.push(list[hh*width + ww]); } result.push(tmp); } return result; }Copy the code

Change skin needs

Normalize transforms the image data from a one-dimensional array into a matrix. With the matrix, we can be more convenient to achieve the needs of editing pictures.

First, we simply realized the requirement of a picture skin change, turning all the black in the picture into yellow (the final effect is shown below).

The original image at the top is red, blue, green and black, and the new image at the bottom.

The peeling function is responsible for changing the color of the image.

Look at the code, and since the RGB value of black is (0,0,0). Then just determine that the pixel is black and reset its RGB value to (255,255,0) to change all the black in the image to yellow.

const dom = document.getElementById("myCanvas"); / / canvas, canvas getImageData (dom, ". / img/the rect. PNG "), then ((data) = > {data = peeling (data, dom. Width, dom. Height); / / avi drawImage (dom, data); }) Peeling (data,w,h){data = normalize(data,w,h); // convert black to yellow (0,0,0) -> (255,255,0) for(let I = 0; i<data.length; i++){ for(let j = 0; j<data[i].length; J++) {/ / exclude comparison of transparency if (data [I] [j]. Slice (0, 3). The join (" ") = = "000") {data [I] [j] = [255255, 0, data [I] [j] [3]]. } } } return restoreData(data); // Convert to a one-dimensional array}Copy the code

Once the matrix is finished, you need to call the restoreData function to convert the multidimensional array back to the one-dimensional array for browser rendering.

function restoreData(data){ const result = []; for(let i = 0; i<data.length; i++){ for(let j = 0; j<data[i].length; j++){ result.push(data[i][j][0],data[i][j][1],data[i][j][2],data[i][j][3]); } } return result; }Copy the code

Apply colours to a drawing pictures

After the data processing is completed, it is also necessary to pass the processed data data to the drawImage function to render a new image (the code is as follows).

Rendering an image mainly calls the following two apis.

  • ctx.createImageData. Create a new white spaceImageDataObject, through.data.setReassign.
  • ctx.putImageData. Draw pixel data onto the canvas.
const dom = document.getElementById("myCanvas"); / / canvas, canvas getImageData (dom, ". / img/the rect. PNG "), then ((data) = > {data = peeling (data, dom. Width, dom. Height); / / avi drawImage (dom, data); }) function drawImage(dom,data){const CTX = dom.getContext("2d"); const matrix_obj = ctx.createImageData(dom.width,dom.height); matrix_obj.data.set(data); CTX. PutImageData (matrix_obj, 0, 0); }Copy the code

The new image has been successfully rendered, as shown below.

Reviewing the above operations, editing an image can be broken down into the following three steps.

  • Convert raw image to matrix data (multi-dimensional array)
  • Manipulate the matrix according to requirements
  • Render the modified matrix data into a new image

The above second step operation is the core of image editing, many complex transformation effects can be achieved by writing matrix algorithm.

To deepen the understanding, use the above knowledge to implement a picture rotation requirements.

Image rotation

Assume the simplest case as shown below, where there are four pixels on the left. The first line has two pixels 1 and 2(here the ordinal number is used instead of the RGBA value).

The second line also has two pixel point 3 and 4. Data sources into a matrix after the data has a value of [[[1], [2]], [[3], [4]]].

How do I turn the left image 90 degrees clockwise to the right?

By looking at the picture, just need to do the transformations of data in the data, make data = [[[1], [2]], [[3], [4]]] into data = [[[3], [1]], [[4], [2]]], you can realize image transform.

Four pixels can be exchanged directly for an array value, but a picture can be hundreds of thousands of pixels. How does that work?

In this case, it is often necessary to write a basic algorithm to realize the rotation of the image.

First look for patterns in the figure below. There are three picture states left, middle and right. To change 1-2-3-4 on the left to 3-1-4-2 on the right, you can do this in two steps.

  • Find the central axis of the height of the matrix, and exchange data on both sides according to the axis. For example, an axis can be drawn between figure 1-2 and figure 3-4 on the left. Data is exchanged around the axis on both sides. The first row becomes 3-4 and the second row becomes 1-2. After the first step, it looks like the middle picture.

  • Diagonal line 3-2 in the middle figure is consistent with the right figure, and the remaining data on both sides of the diagonal can be symmetrically exchanged to form the right figure. For example, swap the values of 1 and 4. After the operation, the rotation of the picture is realized. Note that the index of 4 is [0][1], while that of 1 is [1][0].

By describing the rules above, the following functions can be written to achieve the rotation of the picture.

const dom = document.getElementById("myCanvas"); GetImageData (dom,"./img/rect.png"). Then ((data)=>{data = rotate90(data,dom.width,dom.height); DrawImage (dom,data); }) function rotate90(data,w,h){data = normalize(data,w,h); // Invert around the middle row const mid = h/2; For (hh = 0; hh < mid; hh++){ const symmetric_hh = h - hh -1; // index for(ww = 0; ww<w; ww++){ let tmp = data[hh][ww]; data[hh][ww] = data[symmetric_hh][ww]; data[symmetric_hh][ww] = tmp; For (hh = 0; hh < h; hh++){ for(ww = hh+1; ww<w; ww++){ let tmp = data[hh][ww]; data[hh][ww] = data[ww][hh]; data[ww][hh] = tmp; } } return restoreData(data); // Convert to a one-dimensional array}Copy the code

Since the width and height of the canvas we defined are all 300, the rotation algorithm above only applies to squares (rectangle images need to be written separately).

The final page shows the following rendering (the original image above, the new image generated by the canvas below).