您的位置:首页 > Web前端 > Node.js

PNG图像RGBA转索引色

2016-01-21 14:56 375 查看
       RGBA 转 索引色 的思路有很多种,这里说一种我目前已经用 nodejs 实现的,这个思路是自己摸索出来的,感觉效果还可以。

       以下是大概的思路,实际写的时候还是有些细节差别的。

       1. 先不考虑 alpha ,把 RGB 想象成一个三维色彩空间,三个坐标轴分别为 R、G、B ,颜色值从 0 - 255,这样我们就得到了一个 256 x 256 x 256 的立方体。

       2. 把每个轴以 16 等分,这样就可以得到  16 x 16 x 16 个小格子

       3. 把所有的颜色分别归入这些格子中

       4. 排序所有格子

       5. 每个格子占比 p = 格子中的颜色数/总的颜色数

       6. 每个格子可以分得的颜色个数为 num = 256 x p

       7. 然后把每个小格子等分,等分的标准为,len = Math.ceil(Math.pow(num,1/3)) ,那么这个小格子可以分为 len x len x len 个更小的格子

       8. 把 16 x 16 x 16 格子中的颜色再放入这些更小的格子中

       9. 这些更小的格子的颜色取所有颜色的重心。 (R的和/个数,G的和/个数,B的和/个数)

      10. 每个小格子按照占比再分得一定数量的颜色数(类似上面的 p)

      11. 每个小格子中还要考虑 alpha,然后把 alpha 等分一下,等分的标准为 len = Math.ceil(Math.sqrt(小格子分得的颜色个数))

      12. 然后把 alpha 的分段按照每个段的颜色个数排序一下,把颜色分给排名靠前的

      

      得到调色板后把颜色转为最靠近的颜色,参考了下 PS 里在颜色差得地方有些噪点,所以我加了个随机。在有色差(一个点与周围的点的颜色有差别)的时候加一个随机颜色(取颜色差最小的两个随机一个)

     上一段用 nodejs 写的 RGBA 转 索引色

var useColor = {};
/**
* 计算立方体中的像素分配情况
* @param cubic
*/
var doCubic = function (moreCount) {
if (cubicList.length == 0) {
return;
}
var cubic = cubicList.shift();
var cubicCount = Math.round(allCount * cubic.percent);
if (allCount && !cubicCount) {
cubicCount = 1;
}
cubic.count += cubicCount;
cubic.count += moreCount;
//已经没有可以分配的调色板颜色,则结束
if (cubic.count == 0 && allCount == 0) {
return;
}
if (cubic.count > cubic.list.length) { //如果可分配的颜色数量大于实际颜色数
for (var i = 0; i < cubic.list.length; i++) {
v
4000
ar color = cubic.list[i];
if (useColor[color.a + "." + color.r + "." + color.g + "." + color.b]) continue;
plte.push(color.r << 16 | color.g << 8 | color.b);
trns.push(color.a);
cubic.count--;
allCount--;
useColor[color.a + "." + color.r + "." + color.g + "." + color.b] = true;
}
} else {
//以分配了5个像素为例 : lenCount 为 3,len 为 10, more 为 32 - (3 - 2)*10
var lenCount = Math.ceil(Math.pow(cubic.count, 1 / 3));
var len = Math.floor(deviceCount / lenCount);
var smallCubicList = [];
var more = deviceCount - lenCount * len;
more = deviceCount - (lenCount - more) * len;
for (var r = cubic.r; r < cubic.r + deviceCount;) {
for (var g = cubic.g; g < cubic.g + deviceCount;) {
for (var b = cubic.b; b < cubic.b + deviceCount;) {
smallCubicList.push({r: r, g: g, b: b, list: [], count: 0});
b += len;
if (b < cubic.b + more) {
b += 1;
}
}
g += len;
if (g < cubic.g + more) {
g += 1;
}
}
r += len;
if (r < cubic.r + more) {
r += 1;
}
}
//把立方体中所有颜色放入小格子中
var cubicColors = cubic.list;
for (var i = 0; i < cubicColors.length; i++) {
var cubicColor = cubicColors[i];
var index = -1;
var dis = 256 * 3;
for (var f = 0; f < smallCubicList.length; f++) {
var smallCubicColor = smallCubicList[f];
if (cubicColor.r >= smallCubicColor.r &&
cubicColor.g >= smallCubicColor.g &&
cubicColor.b >= smallCubicColor.b) {
var diff =
Math.abs(cubicColor.r - smallCubicColor.r) +
Math.abs(cubicColor.g - smallCubicColor.g) +
Math.abs(cubicColor.b - smallCubicColor.b);
if (diff < dis) {
dis = diff;
index = f;
}
}
}
var smallCubicColor = smallCubicList[index];
smallCubicColor.list.push(cubicColor);
}
smallCubicList.sort(function (a, b) {
return a.list.length < b.list.length ? 1 : -1;
});
var smallCount = cubic.count;
for (var i = 0; i < smallCubicList.length; i++) {
var smallCubic = smallCubicList[i];
if (smallCubic.list.length * Math.pow(lenCount, 3) > cubic.list.length && smallCount) {
smallCubic.count = 1;
smallCount--;
}
}
var smallAllCount = cubic.list.length;
for (var i = 0; i < smallCubicList.length; i++) {
var smallCubic = smallCubicList[i];
if (smallCount == 0) {
break;
}
var addCount = Math.round(smallCubic.list.length * smallCount / smallAllCount);
if (addCount > smallCount) {
addCount = smallCount;
}
if (smallCount && addCount == 0) {
addCount = 1;
}
smallAllCount -= smallCubic.list.length;
smallCubic.count += addCount;
smallCount -= addCount;
}
var doSmallCubic = function (moreCount) {
if (smallCubicList.length == 0) {
return;
}
var smallCubic = smallCubicList.shift();
smallCubic.count += moreCount;
if (smallCubic.count == 0) {
return;
}
var smallColorListObject = {};
var smallColorList = [];
var alphaListObject = [];
var alphaList = [];
for (var i = 0; i < smallCubic.list.length; i++) {
var color = smallCubic.list[i];
if (smallColorListObject[color.a + "." + color.r + "." + color.g + "." + color.b]) {
continue;
}
smallColorListObject[color.a + "." + color.r + "." + color.g + "." + color.b] = true;
smallColorList.push(color);
if (alphaListObject[color.a]) {
continue;
}
alphaListObject[color.a] = true;
alphaList.push(color.a);
}
//如果小立方体内分配到的像素个数大于等于立方体内的像素个数
if (smallCubic.count >= smallColorList.length) {
for (var i = 0; i < smallColorList.length; i++) {
var color = smallColorList[i];
if (useColor[color.a + "." + color.r + "." + color.g + "." + color.b]) continue;
plte.push(color.r << 16 | color.g << 8 | color.b);
trns.push(color.a);
smallCubic.count--;
cubic.count--;
allCount--;
useColor[color.a + "." + color.r + "." + color.g + "." + color.b] = true;
}
} else {
var smallCubicColor = {r: 0, g: 0, b: 0};
for (var i = 0; i < smallCubic.list.length; i++) {
var color = smallCubic.list[i];
smallCubicColor.r += color.r;
smallCubicColor.g += color.g;
smallCubicColor.b += color.b;
}
smallCubicColor.r = Math.floor(smallCubicColor.r / smallCubic.list.length);
smallCubicColor.g = Math.floor(smallCubicColor.g / smallCubic.list.length);
smallCubicColor.b = Math.floor(smallCubicColor.b / smallCubic.list.length);
if (smallCubic.count >= alphaList.length) {
for (var i = 0; i < alphaList.length; i++) {
if (useColor[alphaList[i] + "." + smallCubicColor.r + "." + smallCubicColor.g + "." + smallCubicColor.b]) continue;
plte.push(smallCubicColor.r << 16 | smallCubicColor.g << 8 | smallCubicColor.b);
trns.push(alphaList[i]);
smallCubic.count--;
cubic.count--;
allCount--;
useColor[alphaList[i] + "." + smallCubicColor.r + "." + smallCubicColor.g + "." + smallCubicColor.b] = true;
}
} else {
var smallLenCount = Math.ceil(Math.sqrt(smallCubic.count));
var smallLen = 256 / smallLenCount;
alphaList = [];
for (var i = 0; i < smallLenCount; i++) {
alphaList[i] = {
a: 0,
count: 0
}
}
for (var i = 0; i < smallCubic.list.length; i++) {
var color = smallCubic.list[i];
var colorIndex = Math.floor(color.a / smallLen);
alphaList[colorIndex].a += color.a;
alphaList[colorIndex].count++;
}
alphaList.sort(function (a, b) {
return a.count < b.count ? 1 : -1;
});
for (var i = 0; i < alphaList.length; i++) {
if (!smallCubic.count) {
continue;
}
if (useColor[alphaList[i].a + "." + smallCubicColor.r + "." + smallCubicColor.g + "." + smallCubicColor.b]) continue;
plte.push(smallCubicColor.r << 16 | smallCubicColor.g << 8 | smallCubicColor.b);
trns.push(alphaList[i].a);
smallCubic.count--;
cubic.count--;
allCount--;
useColor[alphaList[i].a + "." + smallCubicColor.r + "." + smallCubicColor.g + "." + smallCubicColor.b] = true;
}
}
}
doSmallCubic(smallCubic.count);
};
doSmallCubic(0);
}
doCubic(cubic.count);
}
doCubic(0);
var plte2 = [];
for (var i = 0; i < plte.length; i++) {
var color = plte[i];
var a = trns[i];
var r = color >> 16 & 0xFF;
var g = color >> 8 & 0xFF;
var b = color & 0xFF;
plte2.push({a: a, r: r, g: g, b: b});
}
var per = 0;
for (var y = 0; y < h; y++) {
datas[y] = [];
for (var x = 0; x < w; x++) {
var color = colors[y][x];
if (color.a == 0) {
datas[y][x] = 0;
} else {
var index = -1;
var dis = 256 * 4;
for (var i = 0; i < plte2.length; i++) {
var diff = Math.abs(plte2[i].a - color.a) +
Math.abs(plte2[i].r - color.r) +
Math.abs(plte2[i].g - color.g) +
Math.abs(plte2[i].b - color.b);
if (diff < dis) {
dis = diff;
index = i;
if (dis == 0) {
break;
}
}
}
var compareIndexs = [
[-2, -2], [-1, -2], [0, -2], [1, -2], [2, -2],
[-2, -1], [-1, -1], [0, -1], [1, -1], [2, -1],
[-2, 0], [-1, 0], [1, 0], [2, 0],
[-2, 1], [-1, 1], [0, 1], [1, 1], [2, 1],
[-2, 2], [-1, 2], [0, 2], [1, 2], [2, 2]];
var different = false;
for (var i = 0; i < compareIndexs.length; i++) {
var cx = compareIndexs[i][0] + x;
var cy = compareIndexs[i][1] + y;
if (cx < 0 || cy < 0 || cx >= w || cy >= h) {
continue;
}
var compareColor = colors[cy][cx];
if (Math.abs(compareColor.a - color.a) > 1 ||
Math.abs(compareColor.r - color.r) > 1 ||
Math.abs(compareColor.g - color.g) > 1 ||
Math.abs(compareColor.b - color.b) > 1) {
different = true;
break;
}
}
if (different) {
var index2 = -1;
var dis2 = 256 * 4;
for (var i = 0; i < plte2.length; i++) {
var diff = Math.abs(plte2[i].a - color.a) +
Math.abs(plte2[i].r - color.r) +
Math.abs(plte2[i].g - color.g) +
Math.abs(plte2[i].b - color.b);
if (diff < dis2 && diff != dis) {
dis2 = diff;
index2 = i;
}
}
if (dis && Math.abs(dis - dis2) < 10) {
datas[y][x] = [index, index2][Math.floor(Math.random() * 2)];
} else {
datas[y][x] = index;
}
} else {
datas[y][x] = index;
}
}
}
if (Math.floor(y * 100 / h) != per) {
per = Math.floor(y * 100 / h);
//console.log(per + "%");
}
}
//console.log(plte.length, trns.length);
return {
plte: plte, //调色板数组
trns: trns, //调色板透明度数组
colors: datas //转换后的颜色
};
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  nodejs 图像处理