PNG图像RGBA转索引色

       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++) {
                    var 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 //转换后的颜色
        };
    }



猜你喜欢

转载自blog.csdn.net/jiexiaopei_2004/article/details/50555143