文章目录

  • rgba概念
  • 图片转为数据
  • 数据格式转化
  • 换肤功能实现
  • 渲染最终图片
  • 旋转功能实现
  • 局部反相效果

rgba概念

图片是由一个个像素点组成.每一个像素点包含四个值,决定了渲染出来的状态.这四个值为rgba(red, green, blue, alpha).

前三个值是红绿蓝,值的大小范围从0到255,或者从0%到100%之间.

第四个值alpha,规定了色彩的透明度,它的范围为0到1之间.其中0代表完全透明,1代表完全可见.

红绿蓝是色彩中的三元色,通过设置这三种颜色所占的比重,可以变幻出其他所有颜色.

既然每个像素点可以通过rgba的值来表达,那么一张图片所包含的所有像素点都可以转换成数据.如果修改某部分像素点的rgba值,那该图片渲染出来的效果就会发生变化,这样便实现了图片的编辑.

那怎么把图片转化成由像素点组成的数据呢?

图片转为数据

一段简单的html结构如下,页面上放置一个原始图片和一个canvas画布,宽高都为300;

<body><p class="image"><img src="./img/rect.png" width="300" height="300" /></p><canvas id="myCanvas" width="300" height="300"></canvas>
<body>

首先编写一个getImageData函数将原始图片转化成数据(代码如下).

图片转换成像素数据按以下两步操作.

  • 调用ctx.drawImage(img, x, y, width, height) 用于创建ImageData对象
  • 调用ctx.getImageData(x, y, width, height) 用于从canvas中获取ImageData对象
    const dom = document.getElementById("myCanvas"); // canvas画布getImageData(dom,"./img/rect.png").then((data)=>{console.log(data); // 打印输出像素数据})function getImageData(dom,url){const ctx = dom.getContext("2d");   // 设置在画布上绘图的环境const image = new Image();image.src= url;//获取画布宽高const w = dom.width;const h = dom.height;return new Promise((resolve)=>{image.onload = function(){ctx.drawImage(image, 0, 0 ,w,h);                           // 将图片绘制到画布上const imgData = ctx.getImageData(0,0,w,h);    // 获取画布上的图像像素resolve(imgData.data)  // 获取到的数据为一维数组,包含图像的RGBA四个通道数据ctx.clearRect(0,0,w,h);}     })
}

最终的打印出来的数据结果(data)如下:

data = [255, 255, 255, 255, 255, 61, 61, 255, 255, 0, 0, 255, 255,...]

data是一维数组,数组的前四个值[255, 255, 255, 255]为图片第一个像素点的rgba值(ctx.getImageData返回的透明度大小范围是从0 - 255的),[255, 61, 61, 255]
是图片第二个像素点的rgba值,后面依次类推.如此便成功的将图片转化成了数据.

数据格式转化

虽然图片成功转化成了数据,但这样的数据结构很难操作,我们期待能够将数据结构的表现形式与图片展示效果保持一致.

假如存在四个都是黑色的像素点(如下图),总宽高都为2,值为[0, 0, 0, 255,0, 0, 0, 255,0, 0, 0, 255,0, 0, 0, 255].

通过某个函数转换,数据就变成了下列格式.

[[[0, 0, 0, 255],[[0, 0, 0, 255]]], // 第一行[[0, 0, 0, 255],[[0, 0, 0, 255]]]  // 第二行
]

上列数据格式和图片的展示结构保持了一致,可以很清晰的看出当前图形有多少行,每一行又有多少个像素点,
以及每一个像素点的rgba值.

综合上面描述,可以编写函数normalize(代码如下)实现数据格式的转换.

const dom = document.getElementById("myCanvas"); // canvas画布getImageData(dom,"./img/rect.png").then((data)=>{console.log(normalize(data,dom.width,dom.height)); // 打印格式化后的像素数据
})function normalize(data,width,height){const list = [];const result = [];const len = Math.ceil(data.length/4);// 将每一个像素点的rgba四个值组合在一起for(i = 0;i<len;i++){const start = i*4;list.push([data[start],data[start+1],data[start+2],data[start+3]]);}//根据图形的宽和高将数据进行分类for(hh = 0;hh < height;hh++){const tmp = [];for(ww = 0; ww < width;ww++){tmp.push(list[hh*width + ww]);}result.push(tmp);}return result;
}

换肤功能实现

通过normalize函数的转换,一维数组的图片数据转换成了矩阵形式.有了矩阵,我们就可以更加方便的实现编辑图片的需求.

首选我们简单实现一个图片换肤的需求,将图片中的黑色全部变成黄色(最终效果图如下).

上方的原始图片包含红蓝绿黑四种颜色,下方是换肤后生成的新图片.

实现代码如下,peeling函数负责变换图片的颜色.

观察代码,由于黑色的rgb值是(0,0,0).那么只需要判断出像素点是黑色,就重置其rgb值为(255,255,0)便能将图片中所有的黑色换成黄色.

const dom = document.getElementById("myCanvas"); // canvas画布getImageData(dom,"./img/rect.png").then((data)=>{data = peeling(data,dom.width,dom.height); // 换肤drawImage(dom,data); // 绘制图像
})function peeling(data,w,h){data = normalize(data,w,h); // 转化成多维数组// 将黑色变成黄色 (0,0,0) -> (255,255,0)   for(let i = 0;i<data.length;i++){for(let j = 0;j<data[i].length;j++){//排除透明度的比较if(data[i][j].slice(0,3).join("") == "000"){data[i][j] = [255,255,0,data[i][j][3]];}}}return restoreData(data); // 转化成一维数组
}

矩阵的数据操作完了,还需要调用restoreData函数将多维数组再转回一维数组传给浏览器渲染.

 function restoreData(data){const result = [];for(let i = 0;i<data.length;i++){for(let j = 0;j<data[i].length;j++){result.push(data[i][j][0],data[i][j][1],data[i][j][2],data[i][j][3]);}}return result;}

渲染最终图片

数据处理完毕后,还需将处理完的数据data传递给drawImage函数渲染成新图片(代码如下).

渲染图像主要调用以下两个api.

  • ctx.createImageData(width, height) 创建新的空白ImageData对象,通过.data.set重新赋值.
  • ctx.putImageData(imagedata, x, y, dx, dy, width, height) 用于将ImagaData对象的数据填写到canvas中,起到覆盖canvas中原图像的作用,可以只输入前三个参数。参数分别是:用于提供填充图像数据的imagedata对象,imagedata对象左上角相对于canvas左上角的坐标x,y,在canvas上用来填充imagedata区域的左上角相对imagedata对象左上角的坐标x,y(相对于canvas左上角),填充区域的长度和宽度。具体用法效果往下看。
const dom = document.getElementById("myCanvas"); // canvas画布getImageData(dom,"./img/rect.png").then((data)=>{data = peeling(data,dom.width,dom.height); // 换肤drawImage(dom,data); // 绘制图像
})function drawImage(dom,data){const ctx = dom.getContext("2d");const matrix_obj = ctx.createImageData(dom.width,dom.height);matrix_obj.data.set(data);ctx.putImageData(matrix_obj,0,0);
}

至此新图片便成功渲染了出来,效果图可自己实践下获得

回顾上述操作,编辑图像主要分解成以下三步.

  • 将原始图片转化成矩阵数据(多维数组)
  • 依据需求操作矩阵
  • 将修改后的矩阵数据渲染成新图片

上述第二步操作是图像编辑的核心,很多复杂的变换效果可以通过编写矩阵算法实现.

为了加深理解,利用上述知识点实现一个图片旋转的需求.

旋转功能实现

假定存在最简单的情况如下图所示,其中左图存在四个像素点.第一行有两个像素点1和2(这里用序号代替rgba值).

第二行也有两个像素点3和4.数据源转换成矩阵data后的值为 [[[1],[2]],[[3],[4]]].

如何将左图按顺时针旋转90度变成右图?

通过观察图中位置关系,只需要将data中的数据做位置变换,让data = [[[1],[2]],[[3],[4]]]变成data = [[[3],[1]],[[4],[2]]],就可以实现图片变换.

四个像素点可以直接用索引交换数组的值,但一张图片动辄几十万个像素,那该如何进行操作?

这种情况下通常需要编写一个基础算法来实现图片的旋转.

首先从下图中寻找规律,图中有左 - 中 - 右三种图片状态,为了从左图的1-2-3-4变成右图的3-1-4-2,可以通过以下两步实现.

  • 寻找矩阵的高度的中心轴线,上下两侧按照轴线进行数据交换.比如左图1 - 2和3 - 4之间可以画一条轴线,上下两侧围绕轴线交换数据,第一行变成了3 - 4,第二行变成了1 - 2.通过第一步操作变成了中图的样子.

  • 中图的对角线3 - 2和右图一致,剩下的将对角线两侧的数据对称交换就可以变成右图.比如将中图的1和4进行值交换.操作完后便实现了图片的旋转.值得注意的是4的数组索引是[0][1],而1的索引是[1][0],刚好索引顺序颠倒.

通过以上描述规律便可编写下面函数实现图片的旋转.

const dom = document.getElementById("myCanvas"); // canvas画布// getImageData 获取像素数据
getImageData(dom,"./img/rect.png").then((data)=>{data = rotate90(data,dom.width,dom.height); // 顺时针旋转90度drawImage(dom,data); // 绘制图像
})function rotate90(data,w,h){data = normalize(data,w,h); // 转化成矩阵// 围绕中间行上下颠倒const mid = h/2; // 找出中间行for(hh = 0;hh < mid;hh++){const symmetric_hh = h - hh -1; // 对称行的索引for(ww = 0;ww<w;ww++){let tmp = data[hh][ww];data[hh][ww] = data[symmetric_hh][ww];data[symmetric_hh][ww] = tmp;}}// 根据对角线进行值交换for(hh = 0;hh < h;hh++){for(ww = hh+1;ww<w;ww++){let tmp = data[hh][ww];data[hh][ww] = data[ww][hh];data[ww][hh] = tmp;}}return restoreData(data); // 转化成一维数组
}

由于我们定义的canvas宽高都为300,上面的旋转算法只适用于正方形(长方形的图片要另外编写).

局部反相效果

实现思路是将图片画到canvas上,获取canvas的ImageData对象,对每个像素的颜色值进行反相处理。

<script type="text/javascript">  /*   * @param {object} img 展示反相的图片   */function showRevertPic(img){img.color = img.src;    // 给img添加属性指向源文件img.revert = createRevertPic(img);   // 给img添加属性指向反相图片img.onmouseout = function(){this.src = img.revert;}img.onmouseover = function(){this.src = img.color;}img.onmouseout(); // 默认展示一次图片反相}  /*   * @param {object} img 要实现反相的图片   */   function createRevertPic(img){var canvas = document.createElement("canvas");canvas.width = img.width;   canvas.height = img.height;var ctx = canvas.getContext("2d");ctx.drawImage(img,0,0);  var c = ctx.getImageData(0, 0, img.width, img.height);//chrome浏览器报错,ie浏览器报安全错误信息,原因往下看for(var i = 0; i < c.height; ++i){for(var j = 0; j < c.width; ++j){var x = i*4*c.width + 4*j,  //imagedata读取的像素数据存储在data属性里,是从上到下,从左到右的,每个像素需要占用4位数据,分别是r,g,b,alpha透明通道r = c.data[x],g = c.data[x+1],b = c.data[x+2];c.data[x+3] = 150;    //透明度设置为150,0表示完全透明        //图片反相:c.data[x] = 255-r;c.data[x+1] = 255-g;c.data[x+2] = 255-b; }}//ctx.putImageData(c, 40, 40);ctx.putImageData(c,0,0,40,40,200,300);    //裁剪效果见图1return canvas.toDataURL();          //返回canvas图片数据url}window.onload=function() { var img = new Image();img.src = "boy.png";img.isLoad = false;document.body.appendChild(img);img.onload=function(){if(!img.isLoad){showRevertPic(img);img.isLoad=true;}}}
</script>

底片一样的区域就是putImageData放置的区域,鼠标移上去就能看到原来的图片

为什么img的onload函数要设置一个isLoad属性呢,原因你去掉isLoad的判断就知道了,你会发现,我擦咧,图片忽闪忽闪的,这个onload函数居然一直不断的执行下去。

为什么呢,因为showRevertPic(img)默认运行一次mouseout函数,而鼠标移入移出会导致图片的src的改变,每次src改变就会触发onload事件,而onload会导致图片再次反相,于是图片就一直忽闪忽闪的。而查看控制台,img的src一直指向64位编码的png图片数据而没有一次指向原图片地址,原因是当出发了一次mouseout函数img的src就不再指向源文件了,之后的变化是源图片的反相和源图片的反相的反相交替进行。所以给img设置了个isLoad属性是为了只触发一次showRevertPic()函数。

当然去掉showRevertPic()函数中的默认执行一次的mouseout函数也行,但是就不能立马看到图片的反相了。

这里其实存在跨域的问题,当用chrome浏览器或ie浏览器打开(9+)就会报错

chrome:Uncaught SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.ie:SCRIPT5022: DOM Exception: SECURITY_ERR (18)

指向错误愿意来自于getImageData只能操作与脚本位于同一个域中的图片,获取的图片是本地文件夹的,没有域名,所以浏览器认为跨域操作了。所以要感慨下,chrome和ie更注重安全性的问题啊。

解决方法是搭建服务器环境,将文件放到服务器目录下,通过服务器访问,这样就不会报错了。

现在说下createRevertPic()中的返回值canvas.toDataURL()。

这个方法返回的是canvas编码为图片数据的url,用来生成图片的,默认png格式,也可以通过传递参数改变图片格式,还能改变图片保存的质量。如:canvas.toDataURL(“images/jpeg”,0) ,第一个参数就是把图片编码为jpeg格式,第二个参数(0-1)就是指定图片质量,数值越大质量越高,不过对于image/png格式没得设置图片质量orz。另外,chrome还支持自家的image/webp格式图片,也能设置图片质量。

canvas.toDataURL(“images/jpeg”,0) 图片如下,这码打的可以吧

js操作图片像素进行编辑相关推荐

  1. 在MIDP2.0中操作图片像素

    正文 我们知道,在MIDP1.0中,除非我们利用特定厂商的API(比如Nokia),我们是没法对图片的像素进行操作的,但是在MIDP2.0中,Image和Graphics的功能都大大增强了.比如,我们 ...

  2. 使用js修改图片像素颜色并保存

    个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 需求 有时候,我们通常会有修改图片底色,或者让底色透明,也或者颜色交换的类似的需求,特别是我们 ...

  3. Canvas操作图片像素

      Canvas支持直接处理图片中的像素,通过对图片像素进行各种类型的处理,能够实现不同的图片效果,本文的示例图片来自参考文献1,图片像素处理方法参照的参考文献2-7.   Canvas主要通过两种方 ...

  4. js获取图片像素颜色,修改图片像素颜色

    直接上代码示例 <!DOCTYPE HTML PUBLIC> <html><head><meta charset="utf-8">& ...

  5. 用 canvas 操作图片像素

    canvas 像素操作 在 canvas 中可以使用 context.drawImage(image,dx,dy) 方法将图片绘制在 canvas 上.将图片绘制上去后,还可以使用 context.g ...

  6. Opencv开发笔记(一):操作图片像素

    在很多项目中我们会运用到对图形图像的像素处理,很文章我们来总结以下使用opencv对图片的像素进行操作从而达到自己想要的结果. 所谓的像素是图像的基本元素,从根本来说,一张图片就是由数值组成的矩阵,在 ...

  7. JS操作图片的利器:Jimp VS GM

    前段时间,笔者有一个项目需求,需要在一张图片上面添加文件,并另存为一张新的图片.刚开始的时候,笔者使用的是大名鼎鼎的jimp. . 其功能能满足我的要求,而且其license是MIT,不依赖于第三方的 ...

  8. 使html表格可编辑状态,js+Html实现表格可编辑操作

    本文实例为大家分享了js+Html实现表格可编辑操作的具体代码,供大家参考,具体内容如下 功能描述:单击页面使单元格td变成可编辑状态,输入内容后,当单元格失去焦点时,保存输入的内容. 点击增加行,在 ...

  9. 使用JS操作CSS实现JS改变背景图片

    使用JS操作CSS实现JS改变背景图片 在写一个后台管理系统的界面时候,想要实现每次刷新界面或者访问界面时候会重新加载一张图片并且每次都不一样,于是乎就去想了个方法去实现它,最终方案是通过js来改变d ...

  10. 前端js操作截取/裁剪图片

    前端js操作截取/裁剪图片 主要使用canvas相关api来实现图片裁剪 在vue中使用 <template><div><img :src="imgSrc&qu ...

最新文章

  1. 7-5 符号配对 (20 分)
  2. G.Fast应用将开启 中国光进铜退没白干
  3. RiskSense Spotlight:全球知名开源软件漏洞分析报告
  4. 小程序商城源码,小程序源码带前端+后台+数据库 ,免费分享
  5. MM32F3277替换STM32
  6. 如何用公式计算计算机的及格率,及格率和优秀率公式 在excel中如何计算及...
  7. moss列表 查看字段长度
  8. Crack Theory for IPA
  9. ODL(OpenDayLight)Yang UI界面下发流表(ODL+mininet)
  10. RSA中 底数m和模数 n 不互素是仍然成立
  11. 如何注册腾讯云个人账号(图文教程)
  12. 计算机毕业设计Java高校学生综合评价系统(源码+系统+mysql数据库+Lw文档)
  13. byval 和byref的区别,今天刚明白。
  14. ABAP BTE增强举例
  15. 国家电力项目思路总结
  16. 小波变换(wavelet transform,WT)
  17. C++智能指针之shared_ptr
  18. 拿到PMP认证后,能够帮助到大家的也就这三点!你还不知道吗?
  19. 校企合作案例【新文科背景下“三驾马车”与电子商务实验教学的深度融合】
  20. vue实现数字动态翻牌器

热门文章

  1. 负载均衡性能参数如何测评?
  2. 全国流通经济杂志全国流通经济杂志社全国流通经济编辑部2022年第25期目录
  3. 1168: PIPI的方格
  4. 2011黑帽大会:由黑客操控的世界
  5. mysql 不等于 优化_Mysql优化
  6. Camera-roll,pitch,heading
  7. 《庄子·杂篇·庚桑楚第二十三》
  8. iphone计算机能算度分秒吗,‎App Store 上的“图形计算器”
  9. Unity线性空间UI制作方面总结
  10. 工业级交换机级联介绍