Experimental objective: To convert a chess piece picture into a group of sprit animation pictures suitable for WebGL rendering with the help of Canvas, without using other picture processing tools or referring to other libraries, only using native JS.
The initial image is as follows:
First, image segmentation
Split the initial picture into six pieces of the same size
1. HTML Stage:
1 <! DOCTYPE HTML > 2 < HTML lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title> Handle chess picture </title> 6 </head> 7 <body> 8 <canvas id="can_source" style="z-index: 1; top:2px; left:2px; position: absolute"></canvas><! <canvas id="can_mask" style="z-index: 10; top:2px; left:2px; position: absolute"></canvas><! <canvas id="can_maskbak" style="z-index: 1; top:2px; left:2px; position: absolute"></canvas><! </body> 12 <script><! </script> 14 </ HTML >Copy the code
Three canvas canvases are prepared here, where can_source is the canvas to preview the original image, called the “source canvas”; Can_mask is a transparent background canvas suspended on the top of CAN_source to draw cutting range hints, called “hint canvas”. Can_maskbak is used to delineate the cut range (without actually showing it), called the “range canvas”.
2. Segmentation process:
1 var can_source=document.getElementById("can_source"); 2 var can_mask=document.getElementById("can_mask"); 3 var can_maskbak=document.getElementById("can_maskbak"); 4 var top_res; 5 var width=0,height=0; 6 window.onload=function(){ 7 var img=new Image(); 8 img.src=".. /.. /ASSETS/IMAGE/ICON/chesses.jpg"; 9 img.onload=function(){ 10 width=img.width; Height =img.height; 12 can_source.style.width=width+"px"; // CSS size 13 can_source.style.height=height+"px"; 14 can_source.width=width; // Canvas pixel size 15 can_source.height=height; 16 var con_source=can_source.getContext("2d"); 17 con_source. DrawImage (img, 0, 0). 19 top_res=height+4+"px"; 20 can_maskbak.style.left=width+4+"px"; Can_maskbak.style. width=width+"px"; 22 can_maskbak.style.height=height+"px"; 23 can_maskbak.width=width; 24 can_maskbak.height=height; 25 var con_maskbak=can_maskbak.getContext("2d"); 26 con_maskbak. FillStyle = "rgba (0,0,0,1)"; // fill completely opaque black 27 con_maskbak.fillrect (0,0,width,height); 28 29 can_mask.style.width=width+"px"; 30 can_mask.style.height=height+"px"; 31 can_mask.width=width; 32 can_mask.height=height; 33 var con_mask=can_mask.getContext("2d"); 34 con_mask. FillStyle = "rgba (0,0,0,0)"; 35 con_mask. FillRect (0, 0, width, height); 37 //cutRect(40,10,120,240,256,256); //cutRect(192,10,120,240,256,256); 39 / / cutRect,10,120,240,256,256 (340); 40 cutRect (33241120240256256); 41 cutRect (200241120240256256); 42 cutRect (353241120240256256); 44 43}}Copy the code
3, specific cutting algorithm:
2 function cutRect(x,y,wid,hig,wid2,hig2) 3 {4 // Convert rectangle to path, Then use more general path method processing zone 5 var path = [{x: x, y, y}, {x: x + wid, y, y}, {x: x + wid, y, y + high-value detainee interrogation group (hig)}, {x: x, y, y + high-value detainee interrogation group (hig)}]. 6 var framearea=[x,y,wid,hig]; CutPath (path, frameArea,wid2,hig2); // cutPath(path, frameArea,wid2,hig2) 8 9 } 10 function cutPath(path,framearea,wid2,hig2) 11 { 12 var len=path.length; 13 var con_mask=can_mask.getContext("2d"); 14 con_mask strokeStyle = "rgba (160197232, 1)"; // Wireframe 15 con_mask.beginPath(); 16 for(var i=0; i<len; i++) 17 { 18 var point=path[i]; 19 if(i==0) 20 { 21 con_mask.moveTo(point.x,point.y); 22 } 23 else { 24 con_mask.lineTo(point.x,point.y); 25 } 26 27 } 28 con_mask.closePath(); // Draw the prompt box 29 con_mask.stroke() on the prompt canvas; 30 //con_mask.Path; 31 32 33 var con_maskbak=can_maskbak.getContext("2d"); 34 con_maskbak.beginPath(); 35 con_maskbak. FillStyle = "rgba (0255, 1);" 36 con_maskbak.lineWidth=0; 37 for(var i=0; i<len; i++) 38 { 39 var point=path[i]; 40 con_maskbak.lineTo(point.x,point.y); 41 } 42 con_maskbak.closePath(); 43 con_maskbak.fill(); Var con_source=can_source.getContext("2d"); 46 var data_source=con_source.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]); Var datA_maskbak = con_maskbak.getimageData (frameArea [0], frameArea [1], frameArea [2], frameArea [3]); 49 var can_temp=document.createElement("canvas"); // Create a temporary canvas as a tool without actually displaying it. 50 can_temp.width=wid2||framearea[2]; // Set the size of the temporary canvas, save the rectangular cutout as a square! 51 can_temp.height=hig2||framearea[3]; 52 var con_temp=can_temp.getContext("2d"); 53 con_temp. FillStyle = "rgba (255255255, 1)"; 54 con_temp. FillRect (0, 0, can_temp. Width, can_temp height); 55 var data_res=con_temp.createImageData(framearea[2],framearea[3]); Var len=data_maskbak.data.length; 59 for(var i=0; i<len; I +=4)// For each pixel of the scope canvas 60 {61 if(data_maskbak.data[I +1]=255)// If this pixel is green 62 {63 data_res.data[I]=(data_source.data[I]); Data_res.data [I +1]=(data_source.data[I +1]); 65 data_res.data[i+2]=(data_source.data[i+2]); 66 data_res.data[i+3]=(data_source.data[i+3]); 67 } 68 else 69 { 70 data_res.data[i]=(255); // Otherwise fill it with completely opaque white, note that the opacity channel is 0 to 1 in the RGBA representation and 0 to 255 in the data representation! 71 data_res.data[i+1]=(255); 72 data_res.data[i+2]=(255); 73 data_res.data[i+3]=(255); 74} 75} 76 PutImageData (datA_res,(can_temp.width-frameArea [2])/2,(can_temp.height-frameArea [3])/2)// Places the filled pixel data in the middle of the temporary canvas 77 console.log(can_temp.toDataURL()); DataUrl = dataUrl = dataUrl = dataUrlCopy the code
4, the cutting effect is as follows:
In the console you can find the image data as text:
For the image data less than 2MB, directly copy dataUrl paste to the browser address bar press enter, you can display the complete picture, then right-click to save; For image data larger than 2MB, can_temp should be displayed, then right-click save. Sprite single-frame images are generally small, so the need to display CAN_temp is not considered.
The final “soldier” picture:
5, improve
In fact, the Canvas path object itself has a clip method, which can be used to simplify the above process.
Clip method documentation: www.w3school.com.cn/tags/canvas…
Second, generate Sprite animation
1. HTML stage and code preparation:
1 <! DOCTYPE HTML > 2 < HTML lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title> </title> 6 </head> 7 <body> 8 <canvas id="can_back" style="z-index: 1; top:2px; left:2px; position: absolute"></canvas><! <canvas id="can_back2" style="z-index: 1; top:2px; left:2px; position: absolute"></canvas> 10 <canvas id="can_res" style="z-index: 1; top:2px; left:2px; position: absolute"></canvas><! </body> 12 <script> 13 var can_back= document.getelementbyid ("can_back"); 14 var can_back2=document.getElementById("can_back2"); 15 var can_res=document.getElementById("can_res"); 16 var width=240,height=360; 17 window.onload=function(){18 console.log(" start ") {19 can_back.style.width=width+"px"; 20 can_back.width=width; 21 can_back.style.height=height+"px"; 22 can_back.height=height; 23 can_back2.style.width=width+"px"; 24 can_back2.width=width; 25 can_back2.style.height=height+"px"; 26 can_back2.height=height; 27 can_back2.style.left=width+4+"px"; 28 can_res.style.top=height+4+"px"; 29 var img=new Image(); 30 img.src=".. /.. /ASSETS/IMAGE/ICON/bing.png"; Img. onload=function(){} 33 </script> 34 </ HTML >Copy the code
2. Add a “badge” background to the pieces in can_back
The effect is as follows:
For the chess pieces added a shape and color gradient of the emblem background, the emblem is transparent color, according to the power of the chess pieces to set different colors for the emblem. The algorithm first determines whether the pixel of CAN_back is in the pawn. If it is, it is presented as is, otherwise, the color of the pixel is calculated according to the position of the pixel. An implementation method is as follows:
1 var con_back=can_back.getContext("2d"); 2 con_back. FillStyle = "rgba (0, 0255)"; 3 con_back. FillRect (0, 0, width, height); DrawImage (img,(width-256)/2,(height-256)/2) 56 var data_back= con_back.getimagedata (0,0,width,height); 7 //var len=data_back.length; 8 var r1=22/19; 9 var r2=22/51; 10 var p_light=255; Var i_min=0,i_max=0; Var data=data_back.data; 14 for(var i=0; i<360; I++ 15) {var num_w = 16 (math.h pow (110 * 110 - (I - 100) * (I - 100) * r2 * r2, 0.5)); 17 for(var j=0; j<240; Var index=(I *240+j)*4; var index=(I *240+j)*4; 20 if(i<5||i>355) 21 { 22 data[index]=0; 23 data[index+1]=255; 24 data[index+2]=0; 25 data[index+3]=0; 25} 27 else 28 {29 30 if(I <100) 31 {32 if(math.abs (j-119.5)<((i-5)*r1)) 33 {34 If (data [index] + data + data [index + 1] [index + 2] > 600 | | data [index + 3] = = 0) / / is not black or completely transparent 35 {36 var b = 127 + 128 * (95 - I - (5)) / 95; Var b2=(p_light-b)/2; 38 data[index]=b; 39 data[index+1]=b2; 40 data[index+2]=b2; 41 data[index+3]=255; 42 } 43 else 44 { 45 data[index]=0; 46 data[index+1]=0; 47 data[index+2]=0; 48 data[index+3]=255; 49 if(i_min==0) 50 { 51 i_min=i; 52 i_max=i; 53 } 54 else 55 { 56 if(i>i_max) 57 { 58 i_max=i; 59 } 60 } 61 } 62 } 63 else 64 { 65 data[index]=0; 66 data[index+1]=255; 67 data[index+2]=0; 68 data[index+3]=0; If (math.abs (j-119.5)< math.pow ((355-i),0.5)*r2) 70 else 70 {73} 70 else 70 {73} 70 else 70 {73 if(math.abs (j-119.5)< math.pow ((355-i),0.5)*r2) 70 if(math.abs (j-119.5)<num_w) 75 {76 If (data [index] + data + data [index + 1] [index + 2] > 600 | | data [index + 3] = = 0) / / is not black 77 {78 var b = 127 + 128 * (255 - (355 - I)) / 255; 79 var b2=(p_light-b)/2; 80 data[index]=b; 81 data[index+1]=b2; 82 data[index+2]=b2; 83 data[index+3]=255; 84 } 85 else 86 { 87 data[index]=0; 88 data[index+1]=0; 89 data[index+2]=0; 90 data[index+3]=255; 91 if(i_min==0) 92 { 93 i_min=i; 94 i_max=i; 95 } 96 else 97 { 98 if(i>i_max) 99 { 100 i_max=i; 101 } 102 } 103 } 104 } 105 else 106 { 107 data[index]=0; 108 data[index+1]=255; 109 data[index+2]=0; 110 data[index+3]=0; Con_back. PutImageData (data_back,0,0);Copy the code
View Code
3. Stroke the pieces in the badge in can_back2
To prepare for the next step, outline the pieces with an RGB (1,1,1) color and a 2px width edge
1 var size_border=2; 2 var rgb_border={r:1,g:1,b:1}; 3 If (size_border>0) 4 {//- "for specific two color boundary stroke algorithm !!!! 5 console.log(" Start drawing boundaries "); 6 drawBorder (data, 240360, isColorOut isColorIn, Math. The floor (size_border / 2), size_border, rgb_border); 8 var con_back2=can_back2.getContext("2d"); 9 con_back2. PutImageData (data_back, 0, 0).Copy the code
Stroke function:
1 function isColorOut(rgba) 2 { 3 if(rgba.r>127) 4 { 5 return true; 6 } 7 return false; 8 } 9 function isColorIn(rgba) 10 { 11 if(rgba.r==0&&rgba.g==0&&rgba.b==0) 12 { 13 return true; 14 } 15 return false; 16} 17 // Parameters: pixel data, image width, image height, "outer" color (can be multiple), "inner color" (can be multiple, but should not be the same as arr_rgba1!!) 18 // Decide whether to draw the edge inside or outside the offset (default is 0, drawn in the middle? The width of the edge, the color of the edge (considered completely opaque) 19 // Use xy's vertical traversal method, Another idea is to move the computing core along the dividing line - draw smoother 20 //function drawBorder(data,width,height,arr_rgbaout,arr_rgbain,offset_inout,size_border,rgb_border) 21 // The inside and outside colors may be gradient!! So here we use a function that returns a Boolean value !!!! instead of fixing the color range 22 function DrawBorder (data,width,height,func_out,func_in,offset_inout,size_border, rGB_border) 23 {24 // First for each row of pixels 25 for(var i=0;i<height;i++) 26 { 27 var lastRGBA={}; 28 for(var j=0;j<width;j++) 29 { 30 var index=(i*240+j)*4; 31 var RGBA={r:data[index],g:data[index+1],b:data[index+2],a:data[index+3]}; 32 //if(! Lastrgba.r && lastrgba.r!=0)// if the first pixel is 33 If (j==0) 34 {35 lastRGBA=RGBA;// continue; 37} 38 else 39 {40 //if(isRGBAinArr(arr_rgbain, lastRGBA) &&isrGBainarr (arr_rGBain,RGBA) If (func_out(lastRGBA)&&func_in(RGBA))// If the upper color should be on the outside of the stroke, 42 {43 var os_left= math.floor (size_border/2);// to the right 44 var os_right=size_border- OS_left; 45 var j_left=j-os_left; 46 var j_right=j+os_right; 47 j_left+=offset_inout; 48 j_right+=offset_inout; 49 for(var If (k>=0&&k<width) 52 {53 var index2=(I *240+k)*4; 54 data[index2]=rgb_border data[index2+1]=rgb_border.g; 56 data[index2+2]=rgb_border.b; 57 data[index2+3]=255; 58 } 59 60 } 61 } 62 //else If (isRGBAinArr(arr_rgbaout,RGBA)&&isRGBAinArr(arr_rGBain,lastRGBA) If (func_out(RGBA)&&func_in(lastRGBA)) 64 {65 var os_right= math.floor (size_border/2) os_left=size_border-os_right; 67 var j_left=j-os_left; 68 var j_right=j+os_right; 69 j_left-=offset_inout; 70 If (k>=0&&k<width) 74 {75 var index2 = (I *) {if(k>=0&&k<width) 74 {75 var index2 = (I *) 240 + k) * 4; 76 data[index2] = rgb_border.r; 77 data[index2 + 1] = rgb_border.g; 78 data[index2 + 2] = rgb_border.b; 79 Data [index2 + 3] = 255; 80} 81} 82} 83} 84 lastRGBA=RGBA; 85} 86 87} 88 for(var I =0; I <width; I ++) 90 {91 var lastRGBA={}; 92 for(var j=0;j<height;j++)// var index=(j*240+ I)*4; 95 var RGBA={r:data[index],g:data[index+1],b:data[index+2],a:data[index+3]}; 96 //if(! Lastrgba.r && lastrgba.r!=0)// if the first pixel is 97 if(j==0) 98 { 99 lastRGBA=RGBA; 100 continue; 101 } 102 else 103 { 104 //if(isRGBAinArr(arr_rgbain, lastRGBA)&&isRGBAinArr(arr_rGBain,RGBA) If (func_out(lastRGBA)&&func_in(RGBA)) 107 {107 var os_up= math.floor (size_border/2) os_down=size_border-os_up; 109 var j_up=j-os_down; 110 var j_down=j+os_right; 111 j_up+=offset_inout; 112 If (k>=0&&k<height) 116 {117 var if(k>=0&&k<height) 116 {117 var if(k>=0&&k<height index2=(k*240+i)*4; 118 data[index2]=rgb_border.r; 119 data[index2+1]=rgb_border.g; 120 data[index2+2]=rgb_border.b; 121 data[index2+3]=255; 122 } 123 124 } 125 } 126 //else If (isRGBAinArr(arr_rgbaout,RGBA)&&isRGBAinArr(arr_rGBain,lastRGBA) If (func_out(RGBA)&&func_in(lastRGBA)) 128 {var os_right= math.floor (size_border/2) os_left=size_border-os_right; 131 var j_left=j-os_left; 132 var j_right=j+os_right; 133 j_left-=offset_inout; 134 If (k>=0&&k<height) {if(k>=0&&k<height) {if(k>=0&&k<height) {if(k>=0&&k<height) {if(k>=0&&k<height) {if(k>=0&&k<height) {if(k>=0&&k<height) {if(k>=0&&k<height * 240 + i) * 4; 140 data[index2] = rgb_border.r; 141 data[index2 + 1] = rgb_border.g; 142 data[index2 + 2] = rgb_border.b; 143 data[index2 + 3] = 255; 144 } 145 } 146 } 147 } 148 lastRGBA=RGBA; 149 } 150 151 } 152 }Copy the code
Here, all pixels are traversed horizontally and vertically, and strokes are drawn at the inside and outside boundaries of the chess piece outline. The details of the algorithm may be difficult to imagine, so it is suggested to debug the experiment in person. Strokes drawn in this way can be rough.
4. Create animation frames of different states for the chess pieces
Here’s an example of how health changes:
The decrease of the chess piece’s “filling degree” is used to represent the decrease of the chess piece’s health. The image generation algorithm is as follows:
1 console.log(" Start generating health picture "); 2 /* As for the boundary, the top and bottom are inevitable because the proportion of state is reflected vertically. The region between the top and bottom is used to divide the proportion of state 3, and then draw other common edges according to the width of the border. Considering the empty space, The number of vertical and horizontal normal edges is indeterminable 4 - "Stroke operation should be done in the previous step!! ?? */ 5 6 i_min+=size_border; 7 i_max-=size_border; 8 var i_height=i_max-i_min; 9 // Next draw it on a 1800*1800 image (2048*2048 is possible for better performance and clarity, but each cell image size must also be an integer power of 2, such as 256*256), Can_res.style. width=2048+"px"; 11 can_res.width=2048; 12 can_res.style.height=2048+"px"; 13 can_res.height=2048; */ 14 can_res.style.width=1800+"px"; 15 can_res.width=1800; 16 can_res.style.height=1800+"px"; 17 can_res.height=1800; 18 var con_res=can_res.getContext("2d"); 19 //return; 20 //var data=data_back.data; 21 for(var h=10; h>=0; H -) / / health state 11 stages of diminishing 22 {. 23 the console log (" generate "+ h +"/" + 10 + "picture") and var int_x = math.h floor ((10 - h) % 5); 25 var int_y=Math.floor((10-h)/5); 26 if(h==10) 27 { 28 con_res.putImageData(data_back,int_x*360+60,int_y*360); 29 } 30 else 31 { 32 var i_up=Math.floor(i_max-i_height*((h+1)/10)); Var i_down= math.floor (i_max-i_height*((h)/10)+1); 34 for(var i=i_up; i<i_down; Var j_left=0,j_right=0; 37 for(var j=0; j<240; j++) 38 { 39 var index=(i*240+j)*4; 40 if(data[index]==0&&data[index+1]==0&&data[index+2]==0) 41 { 42 if(j_left==0) 43 { 44 j_left=j; 45 data[index]=0; 46 data[index+1]=255; 47 data[index+2]=0; 48 data[index+3]=0; // Set pixel opacity to 0 49} 50 else 51 {52 data[index]=0; 53 data[index+1]=255; 54 data[index+2]=0; 55 data[index+3]=0; 56 j_right=j; 57 } 58 } 59 } 60 /*if(j_right>0) 61 { 62 var index=(i*240+j_right)*4; 63 data[index]=0; 64 data[index+1]=0; 65 data[index+2]=0; 66 data[index+3]=255; Con_res. putImageData(data_back,int_x*360+60,int_y*360); 74 // Fully transparent RGB channels will be discarded when putImageData is used. !!!!! 75} 76 77 78}Copy the code
5. Add the “Destroyed” animation frame
Implementation approach is drawing increasing on pieces of transparent circle said pieces went on, it should be noted because Google browser translucent calculation precision processing, so after considering the possible need to draw translucent circle “die” situation, with opaque green drawing dies first round, then unified green to replace for a precise color of transparency. The implementation code is as follows:
Var h=1; var h=1; h<=5; h++) 3 { 4 var int_x=Math.floor((10+h)%5); 5 var int_y=Math.floor((10+h)/5); 6 con_res.putImageData(data_back,int_x*360+60,int_y*360); 7 con_res. FillStyle = "rgba (0255, 1);" // In order to check the translucency, you can draw a green screen background before show images!! 8 con_res.lineWidth=0; 9 for(var i=0; i<4; i++) 10 { 11 for(var j=0; j<6; j++) 12 { 13 con_res.beginPath(); 14 con_res. Arc (int_x 30 + I * 60 * 60 + 360 +, int_y * 360 + 30 + j * 60 * h, 0, math.h PI * 2); 15 con_res.fill(); 22 var data_res= con_res.getimagedata (0,0,1800,1800); // 23 var len=1800*1800*4; 24 var datar=data_res.data; 25 for(var i=0; i<len; 26 I + = 4) {/ / add breakpoints can lead to the cycle operation timeout if 27 (datar [I] = = 0 && datar [I + 1) = = 255 && datar [I + 2) = = 0) 28 {29 datar [I + 1) = 0. 30 datar[i+3]=0; 31} 32} 33 con_res.putImageData(data_res,0,0);Copy the code
6, use,
After the previous operation we get the Sprite animation picture of the pawn:
Using the same method, we can get Sprite animation pictures for the other five pieces, or add more Sprite animation frames. . We can be in Babylon, js like WebGL engine used in the Sprite images to create a Sprite, can be found here. Babylon, js Sprite document: old documents: ljzc002. Making. IO/BABYLON101 /…