【问题标题】:HTML5 Canvas hue-rotate changing saturation and lightnessHTML5 Canvas 色相旋转改变饱和度和亮度
【发布时间】:2016-11-16 10:18:25
【问题描述】:

我正在开发一个相对简单的应用程序,它将生成相同 .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

顶部的列表包含我想在 .SVG 上添加的实际颜色,底部的方块是我的画布。

如您所见,颜色随着每次迭代越来越多。我在 Photoshop 中检查了确切的颜色(我知道 Photoshop 使用 HSB,但我转换了值)并且 S&L 差异非常大而且有些规律(第一个是正确的)。

  1. 100,82
  2. 100,82
  3. 100,89
  4. 83,100
  5. 52,100
  6. 53,100
  7. 60,100
  8. 62,100

现在,我确实在某处读到不同的浏览器可能会呈现不同的颜色,因此我使用 getPixelData 检查了颜色,结果与我的 Photoshop 读数相符,因此我认为问题确实出在色相旋转滤镜上。

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

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

编辑:here's a fiddle

【问题讨论】:

  • 您可以将图片添加到您的帖子中吗? (而不是链接到它)
  • 抱歉,我需要 10 点声望,我才意识到我使用了错误的帐户 - 一个全新的帐户。
  • 当您希望过滤器在 rgb 颜色空间中运行时,您是否在线性 RGB 颜色空间(默认)中运行过滤器?如果这不是问题,请创建一个minimal reproducible example,以便我们运行一些东西。
  • 这是fiddle。老实说,我不确定您所说的“使用”色彩空间是什么意思——我不记得画布或其过滤器的任何色彩空间设置。理想情况下,我想专门在 HSL 上操作,但画布使用 RGB 数据。尽管如此,即使考虑到色彩空间的差异,饱和度/亮度失真也非常严重,在最后一个正方形中清晰可见。

标签: javascript html canvas svg


【解决方案1】:

这并不是真正的错误。

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

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

我现在真的没有时间去做,所以我将借用一个已经编写好的 js 实现,它比 Q/A 中的默认实现更好的近似值,由 pixi.js mates 编写,并且只会借助 SVGFilter,向您展示如何将其应用到画布上。

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

// 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)
  ];
}
body{ margin-bottom: 100px}
<!-- 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>

【讨论】:

    猜你喜欢
    • 2020-10-29
    • 2014-07-05
    • 1970-01-01
    • 2012-12-31
    • 1970-01-01
    • 2013-03-03
    • 2018-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多