HTML5实例教程:圣诞彩蛋(二)

网页基本代码

首先编写一个简单的网页:

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset='UTF-8'/>
    <title>圣诞彩蛋</title>
<style>  
    body{
        text-align:center;
        background:#E6E7E8;
    }
</style>  
</head>  
<body>  
<canvas width='512' height='512'></canvas>  
<script>  
    var canvas=document.querySelector('canvas');
    var ctx=canvas.getContext('2d');
    var img=new Image();
    img.onload=function(){
        //something
    };
    img.src='image.png';
</script>  
</body>  
</html>  

这段代码中,我们加载了一张图片image.png,因为浏览器安全策略的原因,在Canvas中使用这张图片将会出现跨域问题,所以我们需要在本地架设服务器软件,然后再进行调试.

创建后台Canvas

将图片转化为圆这一操作对于用户而言是不可见的,我们需要创建一个后台Canvas完成对图片的分割.

创建一个Canvas元素很简单:

document.createElement('canvas')  

由于图片载入完毕后我们才能进行绘制,接下来的代码都将写在img.onload=function(){}中.

写入以下代码,完成对后台Canvas的创建和设置:

var canvas2=document.createElement('canvas');  
canvas2.width=canvas2.height=512;  
var ctx2=canvas2.getContext('2d');  

获取ImageData

现在后台Canvas也创建完毕了,我们可以开始图片处理,在图片处理之前,需要获取图片的数据.

ctx2.drawImage(img,0,0);  
var data=ctx2.getImageData(0,0,512,512).data;  

这段代码把img绘制到canvas2上,然后通过getImageData接口得到整张图片的ImageData对象.

计算颜色平均值

将图片中的某一区域转化为单一颜色块,我们需要计算颜色的平均值.

计算方法很简单,就是将该区域的所有颜色加起来除以总数.

在这里我们不去考虑Alpha通道,只计算Red,Green,Blue通道的数值.

计算方法的伪代码如下:

var red=0,green=0,blue=0;  
var i;  
i=0;  
red+=data[i];  
green+=data[i+1];  
blue+=data[i+2];  
i++;  
...
red/=i;  
green/=i;  
blue/=i;  

图像到圆的转化

处理图像有方法:

  1. 每种圆的尺寸都获取一遍,互不干扰.
  2. 先获取最小尺寸的圆,然后通过圆的数据计算得出稍大圆的数据

这两种方法的主要区别在于,第一种方法可以并行运算,第二种方法由于依赖上一次的运算结果,只能串行运算.

因为我们需要更快的将圆呈现给用户,所以采用第一种方法.

为了更快的操作数组数据,我们使用Uint8ClampedArray对象来替代传统数组(Array).

并且Uint8ClampedArray可以隐式进行Math.round运算,省去了我们对平均值的手动取整.

512px圆

最大的圆平均值计算最简单:

var r512=new Uint8ClampedArray(512/512*3);  
var red=0,green=0,blue=0;  
for(var i=data.length;i-=4;){  
    red+=data[i];
    green+=data[i+1];
    blue+=data[i+2];
}
r512[0]=red/(512*512);  
r512[1]=green/(512*512);  
r512[2]=blue/(512*512);  

为了避免污染作用域,我们将这段代码修改成这样:

var r512=(function(data){  
    var r512=new Uint8ClampedArray(512/512*3);
    var red=0,green=0,blue=0;
    for(var i=data.length;i-=4;){
        red+=data[i];
        green+=data[i+1];
        blue+=data[i+2];
    }
    r512[0]=red/(512*512);
    r512[1]=green/(512*512);
    r512[2]=blue/(512*512);
    return r512;
})(data);
256px圆

256px的圆处理起来相对复杂,我们知道256px256px的圆在512px512px的区域可以显示4个,所以我们需要将图片分为4个区域依次计算平均值.

代码如下:

var r256=(function(data){  
    var r256=new Uint8ClampedArray((512*512)/(256*256)*3);
    var width=2,height=2;
    for(var i=0;i<(512*512)/(256*256);i++){
        var red=0,green=0,blue=0;
        var xBegin=(i%width)*256,yBegin=(Math.floor(i/width))*256;
        var xEnd=xBegin+256,yEnd=yBegin+256;
        for(var x=xBegin;x<xEnd;x++){
            for(var y=yBegin;y<yEnd;y++){
                var j=y*512*4+x*4;
                red+=data[j];
                green+=data[j+1];
                blue+=data[j+2];
            }
        }
        r256[i*3]=red/(256*256);
        r256[i*3+1]=green/(256*256);
        r256[i*3+2]=blue/(256*256);
    }
    return r256;
})(data);
128px圆

128px的圆处理方式与256px差不多,区别仅在于分割图片的单位.

代码如下:

var r128=(function(data){  
    var r128=new Uint8ClampedArray((512*512)/(128*128)*3);
    var width=4,height=4;
    for(var i=0;i<(512*512)/(128*128);i++){
        var red=0,green=0,blue=0;
        var xBegin=(i%width)*128,
                yBegin=(Math.floor(i/width))*128;
        var xEnd=xBegin+128,
                yEnd=yBegin+128;
        for(var x=xBegin;x<xEnd;x++){
            for(var y=yBegin;y<yEnd;y++){
                var j=y*512*4+x*4;
                red+=data[j];
                green+=data[j+1];
                blue+=data[j+2];
            }
        }
        r128[i*3]=red/(128*128);
        r128[i*3+1]=green/(128*128);
        r128[i*3+2]=blue/(128*128);
    }
    return r128;
})(data);
通用计算函数

由上面3种单位的代码不难看出分割图片的通用性.

所以我们整理一下代码,将单位改为变量.

得到函数r(r2,data):

function r(r2,data){  
    if(r2!=512){
        var rs=r2*r2;
        var points=512*512/rs;
        var r=new Uint8ClampedArray(points*3);
        var size=Math.sqrt(points);
        for(var i=0;i<points;i++){
            var red=0,green=0,blue=0;
            var xBegin=i%size*r2,yBegin=Math.floor(i/size)*r2;
            var xEnd=xBegin+r2,yEnd=yBegin+r2;
            for(var x=xBegin;x<xEnd;x++){
                for(var y=yBegin;y<yEnd;y++){
                    var j=(y*512+x)*4;
                    red+=data[j];
                    green+=data[j+1];
                    blue+=data[j+2];
                }
            }
            r[i*3]=red/rs;
            r[i*3+1]=green/rs;
            r[i*3+2]=blue/rs;
        }
    }else{              
        var r=new Uint8ClampedArray(3);
        var red=0,green=0,blue=0;
        for(var i=data.length;i-=4;){
            red+=data[i];
            green+=data[i+1];
            blue+=data[i+2];
        }
        r[0]=red/262144;
        r[1]=green/262144;
        r[2]=blue/262144;
    }
    return r;
}

r2512可进行倒序循环,性能增加,所以为r512独立出了一套代码.

r2!=512是常见情况,所以判断时使用r2!=512而不是r2512,前者经过统计比后者节省2~8ms的时间.

转化结果

原图

512px

256px

128px

64px

32px

16px

8px

4px

2px(原Flash没有这种尺寸)

NEXT

在下一个教程中,将完成鼠标事件的响应和初级的动画.

HTML5实例教程:圣诞彩蛋(三)