HTML5 Canvas色调旋转改变饱和度和亮度

时间:2016-11-15 00:26:11

标签: javascript html5 canvas svg

我正在开发一个相对简单的应用程序,它将生成相同.SVG图像的不同颜色版本(由HSL值修改)。

现在我正在实施色调变化。我正在使用生成的颜色列表。在绘制颜色变化之前,选择基色。在这种情况下,我使用了一个简单的.SVG绿色方块(hsl(137,100%,82%))。

这就是我的代码:

for(let i = 0; i < nColors; i++){
                    ctx.filter = 'hue-rotate('+(palette[i].h-hStart)+'deg)';  
                    ctx.drawImage(img, i*100, 0, 100, 100);      
                    ctx.filter = "none";            
                }

其中:

  • nColors是数组中的颜色数量

  • palette是一个包含属性hsl的对象数组 - 将颜色加入

  • hStart是我图片的基本色调(本例中为137)

我计算当前颜色和基色之间的色调差异,并用该数字旋转画布绘制色调,然后并排绘制正方形。不幸的是,here are my results

enter image description here

顶部的列表包含我要在.SVG上施加的实际颜色,底部的正方形是我的画布。

正如您所看到的,每次迭代时颜色会越来越多地转移。我已经检查了Photoshop中的确切颜色(我知道Photoshop使用的是HSB,但是我转换了值),而且S&amp; L差异非常大且有些规律(第一个是正确的)。

  1. 100,82
  2. 100,82
  3. 100,89
  4. 83100
  5. 52100
  6. 53100
  7. 60100
  8. 62100
  9. 现在,我确实读过不同浏览器可能会以不同方式呈现颜色的地方,所以我使用getPixelData检查颜色,结果与我的Photoshop读数相符,因此我认为问题确实存在于色调旋转滤镜中。

    我可以通过读取所有像素数据并手动更改它来实现相同的结果,但最后我想将每个新图像绘制到一个看不见的大画布并导出高分辨率.PNGs - 它会占用大量CPU资源并需要很长时间。

    它实际上是色调旋转的错误/功能还是我在某处犯了错误?有什么办法可以解决吗?有没有其他方法可以实现相同的结果,同时保持相对简单并坚持向量?

    编辑:here's a fiddle

1 个答案:

答案 0 :(得分:1)

这不是一个真正的错误。

Canvas 2DContext&#39; s filter = CSSFilterFunc将产生与CSS filter: CSSFilterFunc相同的结果,而hue-rotate(angle)函数仅接近此色调 - 旋转:它没有&t将所有RGBA像素转换为HSL值。所以,是的,你会得到错误的结果。

但是,您可以尝试使用SVGFilterMatrix进行近似。原始hue-rotate将产生与CSSFunc相似的结果,但我们可以计算色调旋转并将其应用于colorMatrix。 如果你想写它,这里有一篇文章解释如何做:http://www.graficaobscura.com/matrix/index.html

我现在没有时间去做这件事,所以我将借用一个已经编写过的js实现,其更接近于此Q/A中的默认值,由{ {3}}伙伴,只会告诉你如何在画布上应用它,这要归功于SVGFilter。

请注意,正如@RobertLongson正确指出的那样,您还需要将feColorMatrix元素的pixi.js属性设置为sRGB,因为它默认为linear-sRGB

&#13;
&#13;
// set our SVGfilter's colorMatrix's values
document.getElementById('matrix').setAttribute('values', hueRotate(100));

var cssCtx = CSSFiltered.getContext('2d');
var svgCtx = SVGFiltered.getContext('2d');
var reqctx = requiredRes.getContext('2d');
cssCtx.fillStyle = svgCtx.fillStyle = reqctx.fillStyle = 'hsl(100, 50%, 50%)';
cssCtx.fillRect(0, 0, 100, 100);
svgCtx.fillRect(0, 0, 100, 100);
reqctx.fillRect(0, 0, 100, 100);

// CSSFunc
cssCtx.filter = "hue-rotate(100deg)";
// url func pointing to our SVG Filter
svgCtx.filter = "url(#hue-rotate)";

reqctx.fillStyle = 'hsl(200, 50%, 50%)';

cssCtx.fillRect(100, 0, 100, 100);
svgCtx.fillRect(100, 0, 100, 100);
reqctx.fillRect(100, 0, 100, 100);

var reqdata = reqctx.getImageData(150, 50, 1, 1).data;
var reqHSL = rgbToHsl(reqdata);
console.log('required result : ', 'rgba(' + reqdata.join() + '), hsl(' + reqHSL + ')');
var svgData = svgCtx.getImageData(150, 50, 1, 1).data;
var svgHSL = rgbToHsl(svgData);
console.log('SVGFiltered : ', 'rgba(' + svgData.join() + '), , hsl(' + svgHSL + ')');
// this one throws an security error in Firefox < 52 
var cssData = cssCtx.getImageData(150, 50, 1, 1).data;
var cssHSL = rgbToHsl(cssData);
console.log('CSSFiltered : ', 'rgba(' + cssData.join() + '), hsl(' + cssHSL + ')');


// hueRotate will create a colorMatrix with the hue rotation applied to it
// taken from https://pixijs.github.io/docs/filters_colormatrix_ColorMatrixFilter.js.html
// and therefore from https://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751
function hueRotate(rotation) {
  rotation = (rotation || 0) / 180 * Math.PI;
  var cosR = Math.cos(rotation),
    sinR = Math.sin(rotation),
    sqrt = Math.sqrt;

  var w = 1 / 3,
    sqrW = sqrt(w);
  var a00 = cosR + (1.0 - cosR) * w;
  var a01 = w * (1.0 - cosR) - sqrW * sinR;
  var a02 = w * (1.0 - cosR) + sqrW * sinR;
  var a10 = w * (1.0 - cosR) + sqrW * sinR;
  var a11 = cosR + w * (1.0 - cosR);
  var a12 = w * (1.0 - cosR) - sqrW * sinR;
  var a20 = w * (1.0 - cosR) - sqrW * sinR;
  var a21 = w * (1.0 - cosR) + sqrW * sinR;
  var a22 = cosR + w * (1.0 - cosR);
  var matrix = [
    a00, a01, a02, 0, 0,
    a10, a11, a12, 0, 0,
    a20, a21, a22, 0, 0,
    0, 0, 0, 1, 0,
  ];
  return matrix.join(' ');
}

function rgbToHsl(arr) {
  var r = arr[0] / 255,
    g = arr[1] / 255,
    b = arr[2] / 255;
  var max = Math.max(r, g, b),
    min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if (max == min) {
    h = s = 0;
  } else {
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch (max) {
      case r:
        h = (g - b) / d + (g < b ? 6 : 0);
        break;
      case g:
        h = (b - r) / d + 2;
        break;
      case b:
        h = (r - g) / d + 4;
        break;
    }
    h /= 6;
  }
  return [
    Math.round(h * 360),
    Math.round(s * 100),
    Math.round(l * 100)
  ];
}
&#13;
body{ margin-bottom: 100px}
&#13;
<!-- this is our filter, we'll add the values by js -->
<svg height="0" width="0">
  <filter id="hue-rotate">
    <feColorMatrix in="SourceGraphic" id="matrix" type="matrix" color-interpolation-filters="sRGB" />
  </filter>
</svg>
<p>CSS Filtered :
  <br>
  <canvas id="CSSFiltered" width="200" height="100"></canvas>
</p>
<p>SVG Filtered :
  <br>
  <canvas id="SVGFiltered" width="200" height="100"></canvas>
</p>
<p>Required Result :
  <br>
  <canvas id="requiredRes" width="200" height="100"></canvas>
</p>
&#13;
&#13;
&#13;