将画布颜色限制为特定数组

时间:2013-04-18 15:48:53

标签: javascript canvas colors rgb

如何将画布图像的RGB色彩空间限制为特定的颜色数组? F.ex:

var colors = ['#aaffee','#cc44cc','#00cc55','#0000aa'];
var rgb = [230,111,90];

// match rgb with the colors and return the closest match

当我遍历画布图像数据中的像素时,我需要从此数组获得最接近的匹配。是否有一个聪明的功能可以做到这一点?

3 个答案:

答案 0 :(得分:9)

如何将颜色限制为特定调色板

您必须将每个原始像素映射到最近的调色板颜色。

为此,您实际上计算了色轮上原始颜色和调色板颜色之间的距离。

这是一个例子。原始像素(橙色)在指定的调色板中有每种颜色的箭头(假设我们的调色板有3种指定颜色:红色,绿色,蓝色)。

具有最短箭头长度的调色板颜色代替原始像素。

由于橙红色箭头最短,调色板红色将代替原始橙色。

enter image description here

这是将原始颜色映射到调色板颜色的重要功能:

    // use Euclidian distance to find closest color
    // send in the rgb of the pixel to be substituted
    function mapColorToPalette(red,green,blue){
        var color,diffR,diffG,diffB,diffDistance,mappedColor;
        var distance=25000;
        for(var i=0;i<palette.length;i++){
            color=palette[i];
            diffR=( color.r - red );
            diffG=( color.g - green );
            diffB=( color.b - blue );
            diffDistance = diffR*diffR + diffG*diffG + diffB*diffB;
            if( diffDistance < distance  ){ 
                distance=diffDistance; 
                mappedColor=palette[i]; 
            };
        }
        return(mappedColor);
    }

这是代码和小提琴:http://jsfiddle.net/m1erickson/GWQQH/

注意:您可以使用哈希表,树搜索等来改进此代码。

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; padding:15px; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvasOriginal=document.getElementById("OriginalCanvas");
    var ctx=canvasOriginal.getContext("2d");
    var canvasMapped=document.getElementById("MappedCanvas");
    var ctxMapped=canvasMapped.getContext("2d");

    // draw some off-colored rectangles
    ctx.beginPath();
    ctx.fillStyle="rgb(140,70,60)"; //red-ish
    ctx.rect(10,10,20,20);
    ctx.fill();
    ctx.beginPath();
    ctx.fillStyle="rgb(70,140,60)";  //green-ish
    ctx.rect(10,40,20,20);
    ctx.fill();
    ctx.beginPath();
    ctx.fillStyle="rgb(70,60,140)";  //blue-ish
    ctx.rect(10,70,20,20);
    ctx.fill();

    // create an array of palette colors
    var palette=[{r:255,g:0,b:0},{r:0,g:255,b:0},{r:0,g:0,b:255}];

    // load all pixels into an array
    var imageData=ctx.getImageData(0,0,canvasOriginal.width,canvasOriginal.height);
    var data=imageData.data;

    // rewrite all pixels using only the mapped colors
    var mappedColor;
    for(var i=0; i<data.length; i+=4) {
      mappedColor = mapColorToPalette(data[i], data[i+1], data[i+2]);
      if(data[i+3]>10){
          data[i]   = mappedColor.r;
          data[i+1] = mappedColor.g;
          data[i+2] = mappedColor.b;
      }
    }    
    ctxMapped.putImageData(imageData,0,0);

    // use Euclidian distance to find closest color
    function mapColorToPalette(red,green,blue){
        var color,diffR,diffG,diffB,diffDistance,mappedColor;
        var distance=25000;
        for(var i=0;i<palette.length;i++){
            color=palette[i];
            diffR=( color.r - red );
            diffG=( color.g - green );
            diffB=( color.b - blue );
            diffDistance = diffR*diffR + diffG*diffG + diffB*diffB;
            if( diffDistance < distance  ){ 
                distance=diffDistance; 
                mappedColor=palette[i]; 
            };
        }
        return(mappedColor);
    }


}); // end $(function(){});
</script>

</head>

<body>
    <canvas id="OriginalCanvas" width=60 height=100></canvas>
    <canvas id="MappedCanvas" width=60 height=100></canvas>
</body>
</html>

答案 1 :(得分:0)

最好的方法可能是选择所有颜色上最匹配的颜色

var colors = ['#aaffee','#cc44cc','#00cc55','#0000aa'];
var rgb = [230,111,90];
var best = 768;
var canvasColor = colors[0];
for (i=0; i< colors.length; i++){
    t = 0;
    for (j=0; j<3; j++){
        t += Math.abs(rgb[j] - parseInt(colors[i].substring(j*2+1,j*2+3), 16));
    }
    if (t < best){
        best = t;
        canvasColor = colors[i];
    }   
}

答案 2 :(得分:0)

只是另一种解决方案。为了将rgb转换为hsl,我使用了TinyColor

function fixColors(c) {
    var ctx = c.getContext("2d"),
        colors = [
            { rgb: { r: 255, g: 0,   b: 0   } },
            { rgb: { r: 255, g: 255, b: 0   } },
            { rgb: { r: 0,   g: 255, b: 0   } },
            { rgb: { r: 0,   g: 255, b: 255 } },
            { rgb: { r: 0,   g: 0,   b: 255 } },
            { rgb: { r: 255, g: 0,   b: 255 } },
            { rgb: { r: 255, g: 0,   b: 0   } }
        ],
        pixelData = ctx.getImageData(0, 0, c.width, c.height),
        data = pixelData.data,
        cache = { },
        m = Math,
        i;

    colors.forEach(function(c) {
        c.hsl = rgb2hsl(c.rgb);
    });

    for (i = 0; i < data.length; i += 4) {
        var key = "c_" + data[i + 0] + "_" + data[i + 1] + "_" + data[i + 2],
            originalColor,
            newColor;

        if (!cache.hasOwnProperty(key)) {
            cache[key] = tinycolor({r: data[i + 0], g: data[i + 1], b: data[i + 2]}).toHsl();
        }
        originalColor = cache[key]

        newColor = colors.reduce(function(r, l) {
            return m.abs(r.hsl.h - originalColor.h) < m.abs(l.hsl.h - originalColor.h) ? r : l;
        });

        data[i + 0] = newColor.rgb.r;
        data[i + 1] = newColor.rgb.g;
        data[i + 2] = newColor.rgb.b;
    }

    ctx.putImageData(pixelData, 0, 0);
}

function rgb2hsl(c) {
    return tinycolor(c).toHsl();
}