实验目标:借助canvas把一张国际象棋棋子图片转换为一组适用于WebGL渲染的精灵动画图片,不借助其他图片处理工具,不引用其他库只使用原生js实现。

初始图片如下:

一、图片分割

将初始图片分割为六张大小相同的棋子图片

1、html舞台:

 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_source" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas><!--显示原图的画布-->
9 <canvas id="can_mask" style="z-index: 10;top:2px;left:2px;position: absolute"></canvas><!--显示操作范围提示的画布-->
10 <canvas id="can_maskbak" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas><!--用来划分区域的背景画布-->
11 </body>
12 <script><!--主体代码-->
13 </script>
14 </html>

这里准备了三张canvas画布,其中can_source是预览原图的画布,称为“源画布”;can_mask是悬浮在can_source上层的透明背景画布,用来绘制切割范围提示,称为“提示画布”;can_maskbak用来圈定切割范围(其实可以不显示它),称为“范围画布”。

2、分割流程:

 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;//根据图片尺寸设置画布尺寸
11 height=img.height;
12 can_source.style.width=width+"px";//css尺寸
13 can_source.style.height=height+"px";
14 can_source.width=width;//canvas像素尺寸
15 can_source.height=height;
16 var con_source=can_source.getContext("2d");
17 con_source.drawImage(img,0,0);//显示原图
18
19 top_res=height+4+"px";
20 can_maskbak.style.left=width+4+"px";//把这个圈定范围的画布放在右边,做对比
21 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)";//填充完全不透明的黑色
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);
36 //下面是具体的操作代码
37 //cutRect(40,10,120,240,256,256);//矩形切割
38 //cutRect(192,10,120,240,256,256);
39 //cutRect(340,10,120,240,256,256);
40 cutRect(33,241,120,240,256,256);
41 cutRect(200,241,120,240,256,256);
42 cutRect(353,241,120,240,256,256);
43 }
44 }

3、具体切割算法:

 1 //从一个画布上下文中剪切一块dataUrl
2 function cutRect(x,y,wid,hig,wid2,hig2)
3 {
4 //将矩形转换为路径,然后用更一般化的路径方法处理区域
5 var path=[{x:x,y:y},{x:x+wid,y:y},{x:x+wid,y:y+hig},{x:x,y:y+hig}];
6 var framearea=[x,y,wid,hig];//framearea是操作范围的边界,矩形切割则直接是矩形本身,多边形切割则应是多边形的外切矩形范围
7 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(160,197,232,1)";//线框
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();//在提示画布中绘制提示框
29 con_mask.stroke();
30 //con_mask.Path;
31
32
33 var con_maskbak=can_maskbak.getContext("2d");
34 con_maskbak.beginPath();
35 con_maskbak.fillStyle="rgba(0,255,0,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();//在范围画布中画出切割的范围(纯绿色)
44
45 var con_source=can_source.getContext("2d");
46 var data_source=con_source.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]);//获取源画布在操作范围内的像素
47 var data_maskbak=con_maskbak.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]);//获取范围画布在操作范围内的像素
48
49 var can_temp=document.createElement("canvas");//建立一个暂存canvas作为工具,并不实际显示它。
50 can_temp.width=wid2||framearea[2];//设置暂存画布的尺寸,这里要把长方形的切图保存为正方形!
51 can_temp.height=hig2||framearea[3];
52 var con_temp=can_temp.getContext("2d");
53 con_temp.fillStyle="rgba(255,255,255,1)";
54 con_temp.fillRect(0,0,can_temp.width,can_temp.height);
55 var data_res=con_temp.createImageData(framearea[2],framearea[3]);//建立暂存画布大小的像素数据
56
57
58 var len=data_maskbak.data.length;
59 for(var i=0;i<len;i+=4)//对于范围画布的每一个像素
60 {
61 if(data_maskbak.data[i+1]=255)//如果这个像素是绿色
62 {
63 data_res.data[i]=(data_source.data[i]);//则填充源画布的对应像素
64 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);//否则填充完全不透明的白色,注意不透明度通道在rgba表示中是0到1,在data表示中是0到255!
71 data_res.data[i+1]=(255);
72 data_res.data[i+2]=(255);
73 data_res.data[i+3]=(255);
74 }
75 }
76 con_temp.putImageData(data_res,(can_temp.width-framearea[2])/2,(can_temp.height-framearea[3])/2)//把填充完毕的像素数据放置在暂存画布的中间
77 console.log(can_temp.toDataURL());//以dataUrl方式输出暂存画布的数据
78
79 }

4、切割效果如下:

在控制台里可以找到以文本方式输出的图片数据:

对于小于2MB的图片数据,直接复制dataUrl粘贴到浏览器地址栏回车,即可显示完整图片,之后右键保存;对于大于2MB的图片数据则需把can_temp显示出来,之后右键保存。精灵动画的单帧图片一般较小,所以不考虑需要显示can_temp的情况。

最终获取的一张“兵”图片:

5、改进

其实canvas的path对象本身就有clip方法,可以用这个内置方法简化以上过程。

clip方法的文档:https://www.w3school.com.cn/tags/canvas_clip.asp

二、生成精灵动画

1、html舞台及准备代码:

 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><!--徽章的背景-->
9 <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><!--显示结果-->
11 </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("程序开始")
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";//256*256的图片
31 img.onload=function(){//动画帧生成代码
32 }
33 </script>
34 </html>

2、在can_back中为棋子添加“徽章”背景

添加后效果如下:

为棋子添加了一个形状和颜色渐变的徽章背景,徽章外为透明色,可以根据棋子所属的势力为徽章设置不同的主色调。算法首先判断can_back的像素点是否在棋子“兵”内,如果在棋子内则原样呈现,否则根据像素的位置计算像素的颜色,一种实现方法如下:

  1 var con_back=can_back.getContext("2d");
2 con_back.fillStyle="rgba(0,255,0,0)";
3 con_back.fillRect(0,0,width,height);
4 con_back.drawImage(img,(width-256)/2,(height-256)/2)
5
6 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;//背景强度
11 var i_min=0,i_max=0;
12 //一行一行地处理像素
13 var data=data_back.data;
14 for(var i=0;i<360;i++)
15 {
16 var num_w=(Math.pow(110*110-(i-100)*(i-100)*r2*r2,0.5));
17 for(var j=0;j<240;j++)//对于这一行里的每一个像素
18 {
19 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;
26 }
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[index+1]+data[index+2]>600||data[index+3]==0)//不是黑色或者完全透明
35 {
36 var b=127+128*(95-(i-5))/95;//保持红色为主色调
37 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;
69 }
70 }
71 else
72 {
73 //if(Math.abs(j-119.5)<Math.pow((355-i),0.5)*r2)
74 if(Math.abs(j-119.5)<num_w)
75 {
76 if(data[index]+data[index+1]+data[index+2]>600||data[index+3]==0)//不是黑色
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;
111 }
112 }
113 }
114 }
115 }
116 con_back.putImageData(data_back,0,0);

3、在can_back2为徽章中的棋子描边

为后面的环节做准备,给棋子的轮廓描一层rgb(1,1,1)颜色、2px宽度的边

1 var size_border=2;
2 var rgb_border={r:1,g:1,b:1};
3 if(size_border>0)//为前景和背景的边界描边的算法?
4 {//-》为特定的两种颜色边界描边的算法!!!!
5 console.log("开始描绘边界");
6 drawBorder(data,240,360,isColorOut,isColorIn,Math.floor(size_border/2),size_border,rgb_border);
7 }//参数:像素数据,宽度,高度,判断像素在描边内测的条件,判断像素在描边外侧的条件,描边的偏移,边宽,描边的颜色
8 var con_back2=can_back2.getContext("2d");
9 con_back2.putImageData(data_back,0,0);

描边函数:

  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 //参数:像素数据,图片的宽度,图片的高度,”外部“的颜色(可以有多种),“内部的颜色”(可以有多种,但不应与arr_rgba1重复!!)
18 // ,决定把边画在内部还是外部的偏移(默认为0,画在中间?为正表示向内偏),边的宽度,边的颜色(认为完全不透明)
19 //使用xy的垂直遍历方法,另一种思路是让计算核沿着分界线移动《-绘制的更为平滑
20 //function drawBorder(data,width,height,arr_rgbaout,arr_rgbain,offset_inout,size_border,rgb_border)
21 //内外的颜色可能是渐变的!!所以在这里用返回布尔值的函数做参数!!!!而非固定颜色范围
22 function drawBorder(data,width,height,func_out,func_in,offset_inout,size_border,rgb_border)
23 {
24 //首先对于每一行像素
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)//如果是第一个像素
33 if(j==0)
34 {
35 lastRGBA=RGBA;//上一颜色
36 continue;
37 }
38 else
39 {
40 //if(isRGBAinArr(arr_rgbaout,lastRGBA)&&isRGBAinArr(arr_rgbain,RGBA))//在内外颜色的分界处(左侧)
41 if(func_out(lastRGBA)&&func_in(RGBA))//如果上一颜色应该在描边的外侧,同时当前颜色在描边的内侧
42 {
43 var os_left=Math.floor(size_border/2);//偏右
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 k=j_left;k<j_right;k++)//修正偏右
50 {
51 if(k>=0&&k<width)
52 {
53 var index2=(i*240+k)*4;
54 data[index2]=rgb_border.r;
55 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))//在内外颜色的分界处(右侧)
63 else if(func_out(RGBA)&&func_in(lastRGBA))
64 {
65 var os_right=Math.floor(size_border/2);//偏左
66 var 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 j_right-=offset_inout;
71 for(var k=j_left+1;k<=j_right;k++)//修正偏左
72 {
73 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 //然后对于每一列像素
89 for(var i=0;i<width;i++)
90 {
91 var lastRGBA={};
92 for(var j=0;j<height;j++)//对于这一列中的每个像素
93 {
94 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)//如果是第一个像素
97 if(j==0)
98 {
99 lastRGBA=RGBA;
100 continue;
101 }
102 else
103 {
104 //if(isRGBAinArr(arr_rgbaout,lastRGBA)&&isRGBAinArr(arr_rgbain,RGBA))//在内外颜色的分界处(左侧)
105 if(func_out(lastRGBA)&&func_in(RGBA))
106 {
107 var os_up=Math.floor(size_border/2);//偏下
108 var 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 j_down+=offset_inout;
113 for(var k=j_up;k<j_down;k++)//不修正偏下
114 {
115 if(k>=0&&k<height)
116 {
117 var 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))//在内外颜色的分界处(右侧)
127 else if(func_out(RGBA)&&func_in(lastRGBA))
128 {//下面应该是忘了改变量名
129 var os_right=Math.floor(size_border/2);//偏左
130 var 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 j_right-=offset_inout;
135 for(var k=j_left;k<j_right;k++)//修正偏左
136 {
137 if(k>=0&&k<height)
138 {
139 var index2 = (k * 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 }

这里横竖遍历所有像素,在棋子轮廓内外边界处绘制描边,算法细节可能较难以想象,建议亲自调试实验。使用这种方法绘制的描边可能比较粗糙。

4、为棋子建立不同状态的动画帧

这里以生命值变化为例:

用棋子“填充度”的降低表示棋子生命值的减少,图像生成算法如下:

 1             console.log("开始生成健康状态图片");
2 /*关于边界,因为纵向体现状态比例,所以最上边和最下边是必然存在的,用最上边和最下边之间的区域分割状态比例
3 ,然后再根据边框宽度画其他的普通边,考虑到空洞的情况,纵向和横向的普通边数量是不确定的
4 -》描边的操作应该在前一步进行!!??*/
5
6 i_min+=size_border;
7 i_max-=size_border;
8 var i_height=i_max-i_min;
9 //接下来把它画在1800*1800的图片上(设为2048*2048可能获得更高性能和清晰度,但要求每个单元图片尺寸也必须是2的整数次幂,比如256*256),分为横5竖5最多25个状态
10 /*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--)//健康度状态分十一个阶段递减
22 {
23 console.log("生成"+h+"/"+10+"的图片")
24 var int_x=Math.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));//i偏低,取像素整体偏上
33 var i_down=Math.floor(i_max-i_height*((h)/10)+1);
34 for(var i=i_up;i<i_down;i++)//对于每一行像素
35 {
36 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;//将像素不透明度设为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;
67 }*/
68
69
70 }
71 //描边
72
73 con_res.putImageData(data_back,int_x*360+60,int_y*360);
74 //putImageData时完全透明的rgb通道将被丢弃??!!
75 }
76
77
78 }

5、添加“被破坏”动画帧

实现思路是在棋子上绘制不断增大的透明圆表示棋子的消逝,需要注意的是因为谷歌浏览器无法精确处理半透明计算,所以考虑到以后可能需要绘制半透明的“消逝圆”的情况,先用不透明绿色绘制消逝圆,然后统一把绿色替换为具有精确透明度的颜色。实现代码如下:

 1 //接下来添加5帧栅格式的退出动画
2 for(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(0,255,0,1)";//考虑到对半透明的检查,在show图片时可以先绘制一个绿屏背景!!
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*360+60+30+i*60,int_y*360+30+j*60,6*h,0,Math.PI*2);
15 con_res.fill();//这个方法不能正常呈现a通道
16 }
17
18 }
19 }
20 //将绿幕换成透明
21
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;i+=4)
26 {//这个循环内加断点会导致运算超时
27 if(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);

6、使用

经过前面的操作我们得到了棋子“兵”的精灵动画图片:

使用相同方法,我们可以得到其他五种棋子的精灵动画图片,或者添加更多的精灵动画帧。我们可以在Babylon.js之类WebGL引擎中使用这些精灵动画图片创建精灵动画,可以在这里找到Babylon.js的精灵动画文档:旧版文档:https://ljzc002.github.io/BABYLON101/14Sprites%20-%20Babylon.js%20Documentation.htm,新版文档:https://doc.babylonjs.com/divingDeeper/sprites/sprite_map_animations。(4.2版又有了很多新改变,也许要再次开始文档翻译工作了)

网页小实验——用canvas生成精灵动画图片的更多相关文章

  1. 3D网页小实验-基于多线程和精灵动画实现RTS式单位行为

    一.实验目的: 1.在上一篇的"RTS式单位控制"的基础上添加逻辑线程,为每个单位实现ai计算: 2.用精灵动画为单位的行为显示对应的动作效果. 二.运行效果: 1.场景中的单位分 ...

  2. 微信小程序利用canvas生成海报分享图片

    一 . 效果 这是借用女神照生成的分享的海报,图片来自网络. 新增了poster组件和更新图片自适应 二 . 准备 准备两张图片连接,最好是自己开发账号验证的https图片链接. 三 . 实现思路 其 ...

  3. PHP使用JPG生成GIF动画图片,基于php_imagick_st-Q8.dll

    PHP使用php_imagick_st-Q8.dll类库,把JPG图片连接生成GIF动画图片,需要事先下载好php_imagick_st-Q8.dll,文件,并配置php.ini文件,启用php_im ...

  4. 3D网页小实验-基于Babylon.js与recast.js实现RTS式单位控制

    一.运行效果 1.建立一幅具有地形起伏和不同地貌纹理的地图: 地图中间为凹陷的河道,两角为突出的高地,高地和低地之间以斜坡通道相连. 水下为沙土材质,沙土材质网格贴合地形,河流材质网格则保持水平. 2 ...

  5. 3D网页小实验——将txt配置文本转化为3D陈列室

    设计目标:借鉴前辈编程者的经验将简单的配置文本转化为3D场景,并根据配置文件在场景中加入图片和可播放的视频,最终形成可浏览的3D陈列室. 一.使用效果 1.txt配置文件: (博客园的富文本编辑器会改 ...

  6. 关于微信小程序使用canvas生成图片,内容图片跨域的问题

    最近有个项目是保存为名片(图片),让用户发送给朋友或朋友圈,找了很多方案都不适用,绞尽脑汁之后还是选了使用canvas,但是用这玩意儿生成图片最大的缺点就是,如果你的内容中有图片,并且这个图片是通过外 ...

  7. 微信小游戏开发Canvas资源汇总

    Demo: 微信小程序demo组件:股票分时图 微信小程序小组件:仿直播点赞气泡效果,基于Canvas 优质demo推荐:二维码生成器:使用canvas与纯JS版二维码生成 微信小程序学习用完整dem ...

  8. 【Unity 3D】使用 2DToolkit 插件 制作2D精灵动画

    话说博客传图也太麻烦了吧,一个一个文件一个一个传....为什么不能直接粘贴了,自动上传呢... 刚直接粘贴了,结果一张图没有,又重新截一次图,在传了一次...真是太**了 好了,吐槽完了,开始博客吧 ...

  9. 小程序下载canvas生成图片

    save_share_img:function(img){ var that = this; let { result } = that.data; getData.getData( "sa ...

随机推荐

  1. 开发规范(三)数据库 By 阿里

    建表规约 索引规约 SQL语句 ORM映射

  2. 166个最常用的Linux命令,哪些你还不知道?

    linux命令是对Linux系统进行管理的命令.对于Linux系统来说,无论是中央处理器.内存.磁盘驱动器.键盘.鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心.     线上查 ...

  3. Android Studio连接手机调试教程已决解

    Android Studio连接手机调试教程 Windows电脑连接安卓手机需要下载安装驱动,确保电脑联上网络. 准备条件: 1.电脑上安装应用宝软件. 2.手机开发者选项里面打开USB调试,USB安 ...

  4. 一文掌握XSS

    目录 XSS跨站脚本攻击 1.什么叫跨站脚本攻击? 2.XSS跨站脚本攻击的原理 3.XSS跨站脚本攻击的目的是什么? 4.XSS跨站脚本攻击出现的原因 5.XSS跨站脚本攻击的条件 1.有输入有输出 ...

  5. LVS、Nginx和HAProxy区别

    LVS.Nginx和HAProxy区别 LVS 优点: 高并发连接:LVS基于内核网络层面工作,有超强的承载能力和并发处理能力.单台LVS负载均衡器,可支持上万并发连接. 抗负载能力强:是工作在网络4 ...

  6. 关于java方法重写

    1.子类的方法与父类中的方法有相同的返回类型,相同的方法名称.相同的参数列表 2.子类方法的访问级别不能低于父类方法的访问级别 3.子类方法抛出的异常范围不能大于父类中方法抛出的异常范围

  7. 第九章节 BJROBOT 多点导航【ROS全开源阿克曼转向智能网联无人驾驶车】

    1.把小车平放在地板上,用资料里的虚拟机,打开一个终端 ssh 过去主控端启动roslaunch znjrobot bringup.launch. 2.再打开一个终端,ssh 过去主控端启动 rosl ...

  8. .NET Core 处理 WebAPI JSON 返回烦人的null为空

    前言 项目开发中不管是前台还是后台都会遇到烦人的null,数据库表中字段允许空值,则代码实体类中对应的字段类型为可空类型Nullable<>,如int?,DateTime?,null值字段 ...

  9. python -c 妙用

    前言 python -c 命令还是有用的哈 正文 python的 -c 可以在命令行中调用 python 代码, 实际上 -c 就是 command 的意思 官方文档中解释为(节选自: python ...

  10. Python_列表(list)

    list()类中的提供的操作 1.索引取值 li = [11,22,33,44,55] v1 = li[3] print(li[2]) #索引取出33 print(v1) #索引取出44 print( ...