当前位置 博文首页 > ljzc002:网页小实验——用canvas生成精灵动画图片

    ljzc002:网页小实验——用canvas生成精灵动画图片

    作者:ljzc002 时间:2021-01-30 16:02

    实验目标:借助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(