(源代码见最底下)
实现效果
导入一张图片后,展示该图像所包含的RGB三通道色值分布,并可动态调整区间数来展示直方图。
基本思路
由于JS不能直接读取图像像素,故借助HTML5的canvas标签读取图像文件,通过getImageData() 方法可以实现读取图像的像素(8位无符号整型数组)。获取图像像素的数据(包含RGBA)后,只需遍历数组即可统计其中的RGB三通道的每一位(0 - 255)的个数。最后利用统计的RGB数据,根据区间段渲染直方图。步骤
1. 搭建简单的页面结构
编辑简单的HTML结构如下:
图 1 HTML页面结构
为了便于测试,此处暂时设置画布的宽高为600px×600px,编写结束后,再去除画布的宽度限制即可。
2. 处理图像以及获取像素点数据
编辑我们刚刚引入的calculateColor.js。
通过FileReader读取图片文件并在canvas中回显。通过getImageData获取canvas画布中的每一个像素数据。
图 2 读取RGBA数据
页面初始效果如下:
图 3 页面初始效果
此时,点击“选择文件”按钮并上传一张图片,下方会回显图像。接着右键“检查”(F12)打开浏览器控制台,查看我们在JS代码中的输出。
图 4 控制台输出
可以看到,获取的imgData为0-255的8位无符号整型数组,其中每四位代表一个像素点的RGBA。至此,我们不难统计出所有像素点的出现次数。
验证:我们设置了图片的大小为600px × 600 px 每一个像素有rgba四个分量的值。故600*600*4 = 1440000。刚好等于控制台中显示的数组长度。
如下图所示,我们通过rNumber,gNumber,bNumber三个大小为256的数组来容纳对应值出现的次数。需要注意:由于pxData是有序存储RGBA,故在循环中每隔四个代表一个像素点。
图 5 增加对像素数组的统计
打开控制台,输出如下:
图 6 浏览器控制台输出
3. 直方图渲染
但是仅凭一串数据难以反应数据的真实性,我们需要根据数据额外渲染出直方图。这里引入echarts.js(一个使用JavaScript 实现的开源可视化库)来将数据转化为直方图。
由于需要分RGB三个分量进行展示,故定义一个方法,根据数据动态渲染对应的图表:
图 7 初始配置
效果如下:可以看到X轴划分了256个区间,每一个区间对应数组中一个分量的数量。
图 8 初始效果
拓展:压缩图片时,我们可以根据直方图中相邻区间的值合并,从而可以以128,64甚至更少的区间数量rangeNum来展示直方图。根据rangeNum修改后的display方法如下:
图 9 修改后的display方法
效果如下:
图 10 区间数为256
图 11 区间数为64
修改区间数为16:可以看见区间内的总和逐渐增加,且直方图显示从“细致”到“稀疏”变化。
图 12 区间数为16
最终代码
HTML
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>彩色图像的直方图计算</title><style>h1 {text-align: center;}p {float: right;}#myCanvas {display: block;margin: 0 auto;}.container {display: flex;width: 100%;flex-wrap: wrap;}@media screen and (min-width:300px) {.echart-box {width: 100%;height: 500px;}}@media screen and (min-width:768px) {.echart-box {width: 45%;height: 500px;}}@media screen and (min-width:1024px) {.echart-box {width: 30%;height: 500px;}}</style></head><body><div><h1>彩色图像的直方图计算</h1><p>假如皮卡会coding</p><input type="file" id="imageInput" /><br>直方图区间数:<input type="number" id="numInput" value="64"> (x>0&&x<=256) </div> <canvas id="myCanvas" width="600"height="600"></canvas><div class="container"><div class="echart-box" id="rchart"></div><div class="echart-box" id="gchart"></div><div class="echart-box" id="bchart"></div></div></div><script src="/npm/echarts@5.3.1/dist/echarts.min.js"></script><script src="./js/calculateColor.js"></script></body></html>
JavaScript
(function () {var imageInput = document.getElementById("imageInput"); //获取输入按钮var numInput =document.getElementById("numInput");var myCanvas = document.getElementById("myCanvas"); //获取canvasvar cxt = myCanvas.getContext('2d'); //获取上下文var img = new Image(); //定义图像对象var pxData = null; //定义存储像素的数组var rNumber = new Array(256).fill(0); //定义每一个像素点中R通道值的出现次数var gNumber = new Array(256).fill(0); //定义每一个像素点中R通道值的出现次数var bNumber = new Array(256).fill(0); //定义每一个像素点中R通道值的出现次数var rangeNum = 64;//显示直方图的频段//读取文件后的回调函数imageInput.onchange = function () {// img = new Image();var file = this.files[0];if (window.FileReader) {var reader = new FileReader();reader.readAsDataURL(file);//监听文件读取结束后事件reader.onloadend = function (e) {img.src = e.target.result; //修改图像数据};}};numInput.onchange = function(){rangeNum = parseInt(numInput.value);if(pxData){console.log(rNumber);display("rchart",rNumber,"红色直方图分布",['#ff0000']);display("gchart",gNumber,"绿色直方图分布",['#00ff00']);display("bchart",bNumber,"蓝色直方图分布",['#0000ff']);}}//图片读取完毕后,写到canvas里面img.onload = function () {cxt.drawImage(img, 0, 0, myCanvas.width, myCanvas.height);var imgData = cxt.getImageData(0, 0, myCanvas.width, myCanvas.height);pxData = imgData.data; //获取每一个像素console.log("imgData", pxData);// 统计每一个像素点的RGB三通道rNumber = new Array(256).fill(0);gNumber = new Array(256).fill(0);bNumber = new Array(256).fill(0);for (let i = 0; i < pxData.length; i += 4) {rNumber[pxData[i]]++;gNumber[pxData[i + 1]]++;bNumber[pxData[i + 2]]++;}console.log("rNumber:", rNumber);console.log("gNumber:", gNumber);console.log("bNumber:", bNumber);display("rchart",rNumber,"红色直方图分布",['#ff0000']);display("gchart",gNumber,"绿色直方图分布",['#00ff00']);display("bchart",bNumber,"蓝色直方图分布",['#0000ff']);}function display(id,data,title,color) {// 基于准备好的dom,初始化echarts实例var myChart = echarts.init(document.getElementById(id));let ranges = new Array(rangeNum).fill(0),displayData = new Array(rangeNum).fill(0);let delt = Math.ceil(256/rangeNum);for(let i=0;i<rangeNum;i++){ranges[i] = i ;for(let j=0;j<delt;j++){displayData[i] += data[i*delt+j];}}// console.log(ranges);myChart.setOption({title: {text: title,},tooltip:{},//悬浮显示xAxis: {data: ranges},yAxis: {type:"value",//y轴显示区间分量的个数},series: [{name: '个数',type: 'bar',color:color,data: displayData}]},rangeNum);}})();