[JavaScript/canvas] 创建基于坐标访问的图形数据对象

在HTML5中,canvas节点所提供的2d上下文(context),具备一组对像素进行操作的方法:getImageData/putImageData/createImageData。通过这三个方法,我们可以进行像素级别的图像处理。比如可以移植传统的各种滤镜算法,在web页面中开发出一套类似于PS的图片处理应用,等等。

一般来讲,我们首先会通过getImageData方法,获取canvas上一个区域的像素数据。例如:

context.getImageData(10, 10, 100, 80);

这样我们会得到坐标为{10, 10},宽100、高80的一个区域的像素。返回对象ImageData有三个属性:width/height/data:

 

image

 

这里需要注意的是data。从上图中我们可以看到,它的数据类型是Uint8ClampedArray,是一个8位的无符号数组。请移步至http://www.khronos.org/registry/typedarray/specs/latest/,了解一下ES5中有关Typed Array的规范。

data是个一维的数组,每4个数字对应一个点的RGBA色值。所以可以知道,这个数组长度应该是总像素数*4。对像素的描述,是从左到右、从上到下逐行扫描的过程。这种存储方式,或者说一维的描述图像的方式,与我们所习惯的二维坐标系方式不同。所以我希望能创建一个对象,实现二维到一维的转换,把自己从苦逼算点的工作中解脱出来;同时也希望通过本文,让各位初学者能够了解对像素如何进行基本的操作。

首先,我设计一个接口,它应该具备以下几个功能:

 

//
interface ISmartImageData{
    long getPointCount(); //获取像素总数
    Color getValue(long index); //获取索引为index的[像素]的色值
    long getPointIndex(x, y); //转换坐标为像素索引
    Color getPointValue(x, y); //获取坐标点的色值
}

 

 

这里我们还需要一个辅助对象Color。Color使用了数组作为基础结构,为了后续一些运算上的方便,绑定了一些额外的方法。

 

//
function _extend(a, b){    //a -> b
    for(var p in a) b[p] = a[p];
}
var _ColorMethods = {
    //两个色值相加
    add : function(v){
        this[0] += v[0];
        this[1] += v[1];
        this[2] += v[2];
        this[3] += v[3];
    },
    //色值平均到c个点
    avg : function(c){
        this[0] = Math.round(this[0] / c);
        this[1] = Math.round(this[1] / c);
        this[2] = Math.round(this[2] / c);
        this[3] = Math.round(this[3] / c);
    },
    //色值是否相等(Alpha值暂时忽略)
    eq : function(v){
        return this[0] === v[0]
            && this[1] === v[1]
            && this[2] === v[2]
            //&& this[3] === v[3]
            ;
    },
    //与色值v比较,各个色值差允许在sub以内。可以作为降噪的一种方式。
    fuzzy : function(v, sub){
        return Math.abs(this[0] - v[0]) <= sub
            && Math.abs(this[1] - v[1]) <= sub
            && Math.abs(this[2] - v[2]) <= sub
            //&& Math.abs(this[3] - v[3]) <= sub
            ;
    }
}
function Color(c){
    _extend(_ColorMethods, c);
    return c;
}

如果需要让Color对象具备更多的方法,可以对_ColorMethods进行扩展。额外说一点:Color使用数组还是{R:0,G:0,B:0,A:0}这种方式,在实际中性能上几乎没有的区别。

现在我们开始构建SmartImageData对象。首先我们使用一个ImageData来初始化。

//
function SmartImageData(imageData){
    this.data = imageData.data;
    this.width = imageData.width;
    this.height = imageData.height;
}

 

在实际操作中发现一个问题。如果你频繁调用imageData原始对象的width/height属性时,会造成性能上的较大损耗。原因不明。所以我将三个属性分别独立出来。下面实现ISmartImageData接口。

//
SmartImageData.prototype = {
    getPointCount : function(){
        return this.width * this.height;
    },
    getValue : function(index){
        var i = index * 4;
        var d = this.data;
        return Color([d[i+0],d[i+1],d[i+2],d[i+3]]);
    },
    getPointIndex : function(x, y){
        return (y * this.width + x) * 4;
    },
    getPointValue : function(x, y){
        var index = this.getPointIndex(x, y);
        return this.getValue(index);
    }
}

 

剩下的工作就没什么悬念了,可以借助这套系统,扩展出一系列的set方法,将处理过的数据,通过putImageData再放回到canvas中。

比如最近在做的马赛克的处理,至于是降低采样,还是算区域的平均值来得到区块的颜色,都可以通过坐标系来实现。

PS1:在刚结束的HTML5开发者嘉年华上,腾讯的工程师们实现了通过摄像头捕捉用户动作、进行体感操作。相信各位在今晚好好学习、通宵的努力,一定也能解决这个难题。

PS2:祝各位七夕快乐!

posted @ 2012-08-23 13:44  MKing's Kindom  阅读(1061)  评论(0编辑  收藏  举报