(源代码见最底下)
实现效果
导入一张图片后,展示该图像所包含的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="https://cdn.jsdelivr.net/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"); //获取canvas
var 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);
}
})();