如何将RGBA风格的颜色转换为帆布友好的颜色?

时间:2016-09-16 21:18:09

标签: canvas

我试图在画布上绘制与计算样式相同的颜色。

对于这样的颜色很好:

rgb(0,255,255)"

但是像

这样的东西

rgba(0,0,0,0)

在画布上是黑色的,但在浏览器中是白色的

1 个答案:

答案 0 :(得分:1)

匹配DOM颜色混合

红色,绿色,蓝色和Alpha通道

在大多数情况下,您在浏览器中看到的所有图形都包含4个通道。前3个是颜色通道,代表每种成分颜色的强度,红色,绿色和蓝色。第四个通道是Alpha通道,表示像素的透明度。存储在存储器中的每个通道都是8位宽,允许256个离散值。对于颜色通道0表示无贡献,255表示全强度。 alpha通道还有256个可能的值,从0完全透明到255完全不透明。但传统的alpha表示为从0到1的单位值。

我们可以使用CSS颜色字符串rgba(red,green,blue,alpha)来表示像素颜色。

来源混合

当像素的α值<1时1(字节值<255)它与下面的像素混合(这由硬件完成),屏幕上的结果像素是两个像素的混合。

混合像素的标准公式基于1984年Porter-Duff compositing

的论文

在最简单的形式中,当将一个像素绘制在另一个像素之上时,使用所有通道的字节值,使用以下过程,并将其称为“source-over”。 ref W3C Simple alpha compositing

// the destination is the pixel being drawn over
var destination = {r : 255, g : 0, b : 0, a : 255}; // red opaque 
// source is the pixel being put on top
var source = {r : 0, g : 255, b : 0, a : 127}; // green about half transparent

// normalised means brought to a unit value ranging between 0-1 inclusive
var ad = destination.a / 255; // normalise the destination alpha
var as = source.a / 255;      // and source

// get the normalised alpha value for the resulting pixel
var ar = as + ad * (1  - as);


// the resulting pixel
var result = {};
// calculate the colour channels.
result.r = (source.r * as  + destination.r * ad * (1 - as)) / ar;
result.g = (source.g * as  + destination.g * ad * (1 - as)) / ar;
result.b = (source.b * as  + destination.b * ad * (1 - as)) / ar;

// calculate the alpha channel
result.a = ar * 255; // bring alpha back to the byte value
                     // Though it may seem silly to convert to 8 bit range
                     // it is important to do so because there is a 
                     // considerable loss of precision in all these 
                     // calculations

// convert to a pixel value a used in 2D context getImageData
var pixel = new Uint8ClampedArray([
    result.r,
    result.g,
    result.b,
    result.a,
]);

将它放入一个将执行相同操作的函数中,再加上两个辅助函数。

function blendSourceOver(s,d){
    var ad = d.a / 255; // normalise the destination alpha
    var as = s.a / 255;      // and source
    var ar = as + ad * (1  - as);
    var r = {};
    r.r = Math.round((s.r * as  + d.r * ad * (1 - as)) / ar);
    r.g = Math.round((s.g * as  + d.g * ad * (1 - as)) / ar);
    r.b = Math.round((s.b * as  + d.b * ad * (1 - as)) / ar);
    r.a = Math.round(ar * 255); 
    return r;
}
function rgbaToColour(col){
    col = col.replace("rgba(","").replace(")","").split(",");
    var r = {};
    r.r = Number(col[0]);
    r.g = Number(col[1]);
    r.b = Number(col[2]);
    r.a = Math.round(Number(col[3]) * 255);
    return r;
}

function colourTorgba(col){
    return `rgba(${col.r},${col.g},${col.b},${col.a / 255})`;
}

在画布上匹配DOM结果。

问题是让画布与DOM匹配。让我们考虑两个元素,一个在另一个元素上。第一个div是红色,第二个是蓝色,alpha为0.5。

DOM颜色混合示例

.exm {  width : 100px; height: 30px; color: white; text-align: center;} 
 <div style = "background : rgba(255, 0, 0, 1);" class = "exm">
     <div style = "background : rgba(0, 0, 255, 0.5); position : relative; top : 0px; left : 0px;"  class = "exm">
          Red + Blue
     </div>
 </div>

产生的颜色是什么,未知?

现在说我们希望向画布渲染产生的颜色。据我所知,没有直接的方法来对颜色进行采样,因此我们必须从已知的方法中创建颜色。

有两种方法可以做到这一点。

通过渲染复制

首先是复制DOM上发生的事情。添加红色,然后在其上面绘制蓝色。

通过复制渲染步骤匹配DOM颜色的示例

var ctx = can.getContext("2d");
ctx.fillStyle = "rgba(255, 0, 0, 1)";
ctx.fillRect(0, 0, 100, 30);
ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
ctx.fillRect(0, 0, 100, 30);
ctx.font = "18px arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText("Canvas", 50, 22);
var dat = ctx.getImageData(1,1,1,1).data;
colResult.textContent = `Resulting colour rgba(${dat[0]},${dat[1]},${dat[2]},${dat[3]/255})`;
.exm {  width : 100px; height: 30px; color: white; text-align: center; font : 18px arial;} 
.text {color : black; font-size: xx-small;}
 <p class="exm text"> Match DOM and Canvas colours via rendering replication</p>

 <div style = "background : rgba(255, 0, 0, 1);"  class = "exm">
     <div style = "background : rgba(0, 0, 255, 0.5); position : relative; top : 0px; left : 0px;" class = "exm"> DOM 
     </div>
 </div>
 <canvas id = "can" width = "100" height = "30"  class = "exm"></canvas>
 <p class="exm text" id="colResult"></p>

按计算

第二种是使用source-over混合函数计算颜色。

使用Porter-Duff "source-over"混合计算颜色的示例。

function blendSourceOver(s,d){
    var ad = d.a / 255; // normalise the destination alpha
    var as = s.a / 255;      // and source
    var ar = as + ad * (1  - as);
    var r = {};
    r.r = Math.round((s.r * as  + d.r * ad * (1 - as)) / ar);
    r.g = Math.round((s.g * as  + d.g * ad * (1 - as)) / ar);
    r.b = Math.round((s.b * as  + d.b * ad * (1 - as)) / ar);
    r.a = Math.round(ar * 255); 
    return r;
}
function rgbaToColour(col){
    col = col.replace("rgba(","").replace(")","").split(",");
    var r = {};
    r.r = Number(col[0]);
    r.g = Number(col[1]);
    r.b = Number(col[2]);
    r.a = Math.round(Number(col[3]) * 255);
    return r;
}

function colourTorgba(col){
    return `rgba(${col.r},${col.g},${col.b},${col.a / 255})`;
}


var  colour = colourTorgba(
                  blendSourceOver(
                      rgbaToColour("rgba(0, 0, 255, 0.5)"), // source
                      rgbaToColour("rgba(255, 0, 0, 1)") // destination
                  )
               );

 
var ctx = can.getContext("2d");
ctx.fillStyle = colour;
ctx.fillRect(0, 0, 100, 30);
ctx.font = "18px arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText("Canvas", 50, 22);
colResult.textContent = "Resulting colour "+colour;
.exm {  width : 100px; height: 30px; color: white; text-align: center; font : 18px arial;} 
.text {color : black; font-size: xx-small;}
 <p class="exm text"> Match DOM and Canvas colours by calculation</p>
 <div style = "background : rgba(255, 0, 0, 1);"  class = "exm">
     <div style = "background : rgba(0, 0, 255, 0.5); position : relative; top : 0px; left : 0px;" class = "exm"> DOM 
     </div>
 </div>
 <canvas id = "can" width = "100" height = "30"  class = "exm"></canvas>
 <p class="exm text" id="colResult"></p>

检查alpha = 1

现在必须小心,因为你所做的所有计算必须总是以alpha值为1的颜色结束。如果没有,你会遗漏一些信息。所有DOM颜色混合的最终结果是Alpha = 1(背景),任何进一步的透明度都超出了DOM的上下文,并且是浏览器窗口的一部分。

计算顺序

如果您希望计算两种以上的颜色,则必须按照DOM中的顺序进行计算。如果订单不正确,那么结果颜色也将不正确。

例如,假设你有一个红色背景,然后是一个0.5 alpha的蓝色div,然后是一个0.5 alpha的绿色div。计算顺序是自下而上。

background = rgbaToColour("rgba(255,0,0,1)");
div1 = rgbaToColour("rgba(0,0,255,0.5)");
div2 = rgbaToColour("rgba(0,255,0,0.5)");

首先,背景和div1然后将其结果与div2

混合
var temp = blendSourceOver(div1,background);   // source then destination
var result = blendSourceOver(div2,temp);    // source then temp destination
console.log(colourTorgb(result)); // => "rgba(63,128,64,1)"

以另一种方式进行操作会产生完全不同的颜色。

var temp = blendSourceOver(div1,background);   // source then destination
var result = blendSourceOver(div1,temp);    // source then temp destination
console.log(colourTorgb(result)); // => "rgba(63,64,128,1)"

进一步阅读

对于所有你需要知道的W3C(悬疑填充)Compositing and Blending Level 2以更详细的方式介绍这个主题。