简体   繁体   中英

Fill with background color, and then overlay a PNG

I'm trying to make a canvas have a background color, and then overlay a PNG above it.

This is the png texture:

在此处输入图片说明

for example, If backgroundColor = #D95753 (bright-red), the canvas filling levels will be:

1.- Setting the background color

在此处输入图片说明

2.- Overlaying the png above the background color

在此处输入图片说明

That's the code I have, but it is not working. you can't see the background color, only the png image.

var background = new Image();
background.src = "http://i.stack.imgur.com/LF1P0.png";
background.height = y; // set it before
background.width = x; // set it before

ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, background.width, background.height);

background.onload = function() {
    ctx.drawImage(background,0,0, background.width, background.height);
    memeEverything();
}

Thanks!

ctx.globalCompositeOperation = "multiply"

You can use a composite method and avoid losing dynamic range as the answer by frnt will do, the alpha at 0.6 will reduce the contrast from the original mask significantly.

There are many blending options but for an image that you do not want to have white show through and only want to add darkness use the blend mode "multiply" This method will give you the best contrast possible.

ctx.fillStyle = ???  // the background colour you want
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); // fill canvas with colour. This is now the destination
// Destination pixels get multiplied by source pixels 
// nC = dC  * (sC / 255);  ( seperate for each channel RGB. no effect on alpha )
// where nC is the new colour, dC is destination colour and, sC is source colour;
// White pixels make no change, all others reduce the the colour
ctx.globalCompositeOperation = "multiply"; 
// draw the image over the top.
ctx.drawImage(img, 0, 0, canvas.width, cvtx.canvas.height);
ctx.globalCompositeOperation = "source-over";  // reset to default

Best Quality

There is a better way but that involves getting the pixel data and the doing a photon count on each pixel before the multiply. This will give a closer match to the original mask. The formula is nC =Math.sqrt(dC * dC * (sC * sC / (255*255))); where nC is the new colour, dC is the destination colour and, sC is the source colour. Apply to each channel RGB ignoring alpha.

// r,g,b are the background colour
// img is a loaded image to convert
var r = ?, g = ?, b = ?;
// create canvas and context for image
var c = document.createElement("canvas");
c.width = img.width;
c.height = img.height;
var ctx = c.getContext("2d");
// draw image onto the canvas
ctx.drawImage(img, 0,0);
// get the pixel data
var data = ctx.getImageData(0,0,c.width,c.height);
var d = data.data;
// Convert background colour to photon count and normalise 
r = r * r / (255 * 255);
g = g * g / (255 * 255);
b = b * b / (255 * 255);
var i = 0, len = d.length;;
// for each pixel do the multiply using photon counts
while(i < len){
    d[i] = Math.sqrt(d[i] * d[i++] * r);
    d[i] = Math.sqrt(d[i] * d[i++] * g);
    d[i] = Math.sqrt(d[i] * d[i++] * b);
    i ++;
}
// put the image data back onto the canvas.
ctx.putImageData(data,0,0);

Via alpha composite.

Multiply may not be the effect you are after you can also use a alpha composite. There are two ways, simple and photon count. Please Note that the two methods assume that the background colour has a alpha value of 255, and will ONLY work for that value.

Simple way get the alpha for the pixel. How it is done if you had the alpha channel set and just used the canvas source-over (default) blending function.

// assume you have got the pixel data etc.. see above snipets
var amount = ?; // the mixing amount
var i = 0, len = d.length;;
// for each pixel do the alpha blend
while(i < len){
    var rr = d[i];   // get source channels
    var gg = d[i + 1]; 
    var bb = d[i + 2]; 
    var alpha = (rr + gg + bb) / ( 3 * 255); // calculate alpha by finding the mean 
    // alpha is inverted  but also need to clamp alpha as floating point may give a value too high so clamp and invert
    alpha = Math.min(1, 1 - alpha);
    alpha *= amount;   // set the mix amount
    var aInv = 1 - alpha; // invert again
    // each channel is the sum of background and image colour time their respective mix amounts
    d[i++] = aInv * r + alpha * rr;  
    d[i++] = aInv * g + alpha * gg;
    d[i++] = aInv * b + alpha * bb;
    d[i++] = 255;  // in this case alpha is always 255
}
// put the image data back onto the canvas.
ctx.putImageData(data,0,0);

And the correct way (how it is done for high quality professional mixing)

// assume you have got the pixel data etc.. see above snipets
var amount = ?; // the mixing amount
// Convert background colour to photon count and normalise 
r = r * r / (255 * 255);
g = g * g / (255 * 255);
b = b * b / (255 * 255);
var i = 0, len = d.length;;
// for each pixel do the alpha blend using photon counts
while(i < len){
    var rr = d[i];   // get source channels
    var gg = d[i + 1]; 
    var bb = d[i + 2]; 
    rr *= rr;
    gg *= gg;
    bb *= bb;
    var alpha = (rr * + gg + bb) / ( 3 * 255 * 255); // calculate alpha by finding the mean in photon space
    // alpha is inverted but also need to clamp alpha as floating point may give a value too high so clamp and invert
    alpha = Math.min(1, 1 - alpha);
    alpha *= amount;   // set the mix amount
    var aInv = 1 - alpha; // invert again
    // each channel is the sum of background and image colour time their respective mix amounts
    d[i++] = Math.sqrt(aInv * r + alpha * rr);  
    d[i++] = Math.sqrt(aInv * g + alpha * gg);
    d[i++] = Math.sqrt(aInv * b + alpha * bb);
    d[i++] = 255;  // in this case alpha is always 255
}
// put the image data back onto the canvas.
ctx.putImageData(data,0,0);

Why use the photon count colour model.

The colour model I use is based on the number of photons emitted by the devices display. Each colour channel holds a value from 0 to 255 but these values do not match the actual output of the monitor, nor do they represent the number of photons captured by the camera (input device). They are the square root of the photon flux. If you mix a colour by simple linear mean and do not take this into account the resulting colours will be darker than they should be (this is particularly noticeable when images have high hue contrast) and the contrast curve will be widened. When you manipulate pixels directly for the best results alway square the r,g,b values, do the mixing, blending, whatever. When you are ready convert the values back to there logarithmic representations by calculating the square root of the result.

This video will explain in more detail Computer Color is Broken

Try that by define fillRect() and color in rgba format as below,

<canvas id="canvs" width="500" height="500">
<img src="http://i.stack.imgur.com/LF1P0.png" id="img">
</canvas>

function onld(){
var can = document.getElementById('canvs');
var img = document.getElementById('img');
var ctx = can.getContext('2d');
ctx.fillStyle = 'rgba(217, 87, 83, 1)';
ctx.fillRect(10,10,500,500);
ctx.globalAlpha=0.6;
ctx.drawImage(img,10,10);
}
window.addEventListener('load',onld);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM