HTML Canvas composition: combining "lighter" and "source-atop" effects - javascript

Essentially, I have a gray-scale image of a cube that I would like to color different colors using an HTML 5 canvas. I don't care much about browser compatibility at the moment, so I've been looking at the globalCompositeOperation property values listed here:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
Essentially, I want to combine the effects of "source-atop" and "lighter". Using just source-atop, I get a blue shape, but the different shades of gray are all filled in with the same shade of blue, so I get a flat, skewed hexagon instead of a cube.
Using just the lighter composite option, I get closer to the effect I want. All the cube faces are differing shades of blue like I want, but the previously transparent background becomes solid blue.
I would love a canvas solution that would produce the cube in the lighter example without the blue background. I realize I could just define the cube's points and use the fill style and paths to create the cube, but I have plans to use more complex icon shapes than a cube, and I don't want to do all that when I already have a gray-scale .png ready unless I absolutely have to.
Code currently is pretty basic
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
var cube = new Image();
cube.src = "url" //path to gray-scale cube image.
ctx.clearRect(0, 0, canvas.width, canvaseheight);
ctx.drawImage(cube, 0, 0);
ctx.globalCompositeOperation = "lighter"; //or "source-atop"
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);

Turns out you can combine them pretty easily. Here is how I ended up accomplishing this task.
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
var cube = new Image();
cube.src = "url" //path to gray-scale cube image.
//get a blue mask that fills the entire cube region
ctx.clearRect(0, 0, canvas.width, canvaseheight);
ctx.drawImage(cube, 0, 0);
ctx.globalCompositeOperation = "source-atop";
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
//save that image off somewhere.
var blueMask = new Image();
blueMask.src = canvas.toDataURL();
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
//draw the cube again
ctx.drawImage(cube, 0,0);
//draw the mask image over it with the 'lighter' composition setting.
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(blueMask, 0, 0);
This produces the desired result.

Related

HTML Canvas - Adjust opacity of multiple elements as a layer [duplicate]

This question already has an answer here:
Is there a way to disable color mixing/overlapping in html canvas
(1 answer)
Closed 7 months ago.
I'm trying to find a way to draw multiple elements onto an HTML canvas, then adjust all of their opacities at once. For example, this codepen example draws two overlapping rectangles with globalAlpha set to 0.5, so they're semi-transparent.
Here's what I see:
Here's what I want to see:
In other words, I want to draw some set of elements, then adjust their alpha/opacity all at once. In the example above, I want the overlapping section of blue & red to appear as just blue, since the blue rectangle was drawn 2nd.
I want this solution to apply to images, shapes, any canvas drawings really.
Does anyone know how to accomplish this using HTML canvas?
you must decompose the process
1- create de canvas with all draw ( alpha 100%)
2 set aside the flattened drawings
3 clear the canvas
4 fetch the picture and add it to the canvas
5 set alpha to 50%
6 add the tmp flattened drawings with alpha
const cnv = document.createElement("canvas");
cnv.width = 300;
cnv.height = 300;
const ctx = cnv.getContext("2d");
document.body.appendChild(cnv);
// Draw red rectangle
ctx.fillStyle = "#f00";
ctx.fillRect(20, 20, cnv.width - 140, cnv.height - 60);
// Draw blue rectangle
ctx.fillStyle = "#00f";
ctx.fillRect(100, 40, cnv.width - 140, cnv.height - 60);
//aside the draw in flatten layer
let tmp = new Image();
tmp.src = cnv.toDataURL();
// clear the canvas
ctx.clearRect(0, 0, cnv.width, cnv.height);
// apply bg
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, cnv.width, cnv.height);
const image = new Image(); // Using optional size for image
image.src = "https://img.photographyblog.com/reviews/kodak_pixpro_fz201/photos/kodak_pixpro_fz201_01.jpg";
image.onload = () => {
// Draw background image
ctx.drawImage(image, 0, 0);
ctx.drawImage(image, 0, 0, cnv.width, cnv.width);
// Set alpha to 0.5
ctx.globalAlpha = 0.5;
//overlay with the tmp flatten img with 50%
ctx.drawImage(tmp, 0, 0, cnv.width, cnv.width);
}

Text over background (Canvas/JS)

I'm trying to place text over a background color. I think the issue is that the "fillStyle" is being applied to both the text and the background. I want the text to be black. What am I doing wrong here?
Below is my code:
var canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");
ctx.fillText("hello", 0, 0);
ctx.fillStyle = "#E7E0CA";
ctx.fillRect(0, 0, 200, 200);
var img = document.createElement("img");
img.src = canvas.toDataURL("image/png");
document.body.appendChild(img);
Here's a link to the fiddle: https://jsfiddle.net/jessecookedesign/9rsy9gjn/36/
Unlike HTML where you define a list of what you want to appear, when working with a canvas it's like you're painting. So each "drawing" operation you do (like fillRect or fillText) will go on top of any existing content and cover it up.
Similarly since you're actually painting, rather than defining objects, you need to set the fill style before drawing. Using the analogy, you need to select the color of paint you'll use before you put paint to canvas.
Finally, the fillText method takes a position as the start of the baseline of the text. Since (0, 0) is the top left of the canvas, your text will get drawn above the bounds of the canvas an not be visible, so you need to move it down e.g. fillText("Hello World", 10, 100);
Correcting for those issues you get something like the following (and skipping the steps involved in converting to an img tag):
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// Draw a black background
context.fillStyle = "black";
context.fillRect(0, 0, 200, 200);
// Draw the text
context.fillStyle = "#E7E0CA";
context.fillText("Hello world", 10, 100);
<canvas id="canvas" width="200" height="200"></canvas>
Wrong order - You're drawing the rectangle over the text.
The text has the same color as the rectangle
There's no font specified
The position (0,0) is out of bounds
Try it like this:
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#E7E0CA";
ctx.fillRect(0, 0, 200, 200);
ctx.fillStyle = "black";
ctx.font="20px Georgia";
ctx.fillText("hello",10,30);
There are several issues:
You need to first fill the background, then fill the text.
Your text is above the canvas area — try a lower position.
This code has the correct order, and a position for the text that isn’t outside the canvas bounds.
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#E7E0CA";
ctx.fillRect(0, 0, 200, 200);
ctx.fillStyle = "#000000";
ctx.fillText("hello", 10, 10);
With the changed order, of course you need to choose a new color for the text, in this case "#000000".
Alternatively, you could save and restore your canvas state:
var ctx = canvas.getContext("2d");
ctx.save();
ctx.fillStyle = "#E7E0CA";
ctx.fillRect(0, 0, 200, 200);
ctx.restore();
ctx.fillText("hello", 10, 10);
Whenever you access canvas of html page,
whatever you draw first, will display first.
so if you want to display your colored box first fill it first, then write your text by providing color ,font and position of text. for example,
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "#E7E0CA";//your rect color
ctx.fillRect(0, 0, 200, 200);//your rect size
ctx.fillStyle = "#000000";//color for your text
ctx.font="30px Arial";//font style and size
ctx.fillText("hello world",25,50);//text and location
</script>

How to select particular area in canvas?

I am new to canvas, I have an image myimg.jpg, I have converted this image into canvas and i am trying to apply some pattern image for heel.
I am not able to do it. Here is my screenshot:
How can I get it done.
<div id="myId">
<canvas id="canvaswrapper" width="660" height="540"></canvas>
</div>
function drawImage(){
var ctx = $("canvas")[0].getContext("2d"),
img = new Image();
img.onload = function(){
ctx.drawImage(img, 0, 0, 500, 500);
ctx.beginPath();
var img2= new Image();
var w;
var h;
img2.src = "http://www.gravatar.com/avatar/e555bd971bc2f4910893cd5b785c30ff?s=128&d=identicon&r=PG";
var pattern = ctx.createPattern(img2, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, w, h);
ctx.arc(300,305,50,0,2*Math.PI);
ctx.fill();
ctx.stroke();
};
img.src = "myimg.jpg";
}
drawImage();
You can define the area you want to fill using an image mask that fits on top of your image - this step is something for Photoshop/GIMP.
For example, having your shoe as-is:
Create a mask for it leaving the heal in the original position (it makes it easier to draw it back in - you can always crop it and draw it using an offset instead). Important: background must be transparent:
Then super-impose the pattern using these steps:
Load the pattern and define is as a fill-pattern
Draw the mask into the empty canvas
Optional step: Adjust transformations if needed (translate, scale)
Choose composite mode "source-atop"
Fill the canvas
Choose composite mode "destination-atop"
Draw the main image on top (which will show behind the mask/pattern)
Optional step: draw in original mask image using blending mode "multiply" to add shadow and highlights (does not work in IE). This will help creating an illusion of depth. For IE, drawing it on top using a reduced alpha or a separate image only containing shadows etc. can be an option
Result
Example
var iShoe = new Image, iMask = new Image, iPatt = new Image, count = 3;
iShoe.onload = iMask.onload = iPatt.onload = loader;
iShoe.src = "http://i.stack.imgur.com/hqL1C.png";
iMask.src = "http://i.stack.imgur.com/k5XWN.png";
iPatt.src = "http://i.stack.imgur.com/CEQ10.png";
function loader() {
if (--count) return; // wait until all images has loaded
var ctx = document.querySelector("canvas").getContext("2d"),
pattern = ctx.createPattern(iPatt, "repeat");
// draw in mask
ctx.drawImage(iMask, 0, 0);
// change comp mode
ctx.globalCompositeOperation = "source-atop";
// fill mask
ctx.scale(0.5, 0.5); // scale: 0.5
ctx.fillStyle = pattern; // remember to double the area to fill:
ctx.fillRect(0, 0, ctx.canvas.width*2, ctx.canvas.height*2);
ctx.setTransform(1,0,0,1,0,0); // reset transform
// draw shoe behind mask
ctx.globalCompositeOperation = "destination-atop";
ctx.drawImage(iShoe, 0, 0);
// to make it more realistic, add mask in blend mode (does not work in IE):
ctx.globalCompositeOperation = "multiply";
if (ctx.globalCompositeOperation === "multiply") {
ctx.drawImage(iMask, 0, 0);
}
}
<canvas width=281 height=340></canvas>

Fill image with texture/pattern

I'm looking for a solution to change the texture/pattern for a product.
At this moment i have:
A .png picture of a couch with a transparent background
A .png picture of a texture
With the following code:
<canvas id="a" width="800" height="500">Canvas not supported on your browser</canvas>
var width = $(window).width();
var height = $(window).height();
var c = document.getElementById("a");
var ctx = c.getContext("2d");
var can2 = document.createElement('canvas');
document.body.appendChild(can2)
can2.width = c.width;
can2.height = c.height;
var ctx2 = can2.getContext("2d");
var test = new Image();
test.src = "Images/newBank.png";
test.onload = function () {
ctx2.drawImage(test, 0, 0);
};
var img = new Image();
img.src = "Images/texturetrans.png";
img.onload = function () {
ctx2.globalCompositeOperation = 'source-in';
var ptrn = ctx2.createPattern(img, 'repeat');
ctx2.fillStyle = ptrn;
ctx2.fillRect(0, 0, can2.width, can2.height);
}
`
I get this result:
As you can see, the whole object is filled with my texture. No definitions of the pillows etc. are visible anymore. Is it possible to let my texture be a sort of transparent mask?
I'm already able to change the color of the couch:
But I'd like to be able to also add a pattern to my couch!
Any help will be appreciated and I'm already very sorry for my bad English.
If you're just after an illustrative approximation you can use a combination of blending and composition modes.
First thing is to make sure your main image has transparency - this is important for composition to work (I made a rough cut-off in the following demo).
Main steps:
Draw the pattern
Draw the main image on top with blending mode multiply
Draw the main image on top with compositing mode destination-in - this will make a cut-out
If you want to reduce the size of the pattern you can either do this by using a smaller version of the image, draw to a temporary canvas at a smaller size and use that as pattern, or use the new transform methods on the pattern itself.
Demo
var img1 = new Image, img2 = new Image, cnt = 2,
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
// image loading for demo (ignore)
img1.onload = img2.onload = function() {if (!--cnt) go()};
img1.src = "//i.imgur.com/8WqH9v4.png"; // sofa
img2.src = "//i.stack.imgur.com/sQlu8.png"; // pattern
// MAIN CODE ---
function go() {
// create a pattern
ctx.fillStyle = ctx.createPattern(img2, "repeat");
// fill canvas with pattern
ctx.fillRect(0, 0, canvas.width, canvas.height);
// use blending mode multiply
ctx.globalCompositeOperation = "multiply";
// draw sofa on top
ctx.drawImage(img1, 0, 0, img1.width*.5, img1.height*.5);
// change composition mode
ctx.globalCompositeOperation = "destination-in";
// draw to cut-out sofa
ctx.drawImage(img1, 0, 0, img1.width*.5, img1.height*.5);
}
<canvas id="canvas" width=600 height=400></canvas>
You can also reverse the order of which image is drawn etc., if you prefer. This is just an example of one way.
If you need accurate texture then there is no way around to either take photos or use a 3D software, or hand-drawn the textures.
NOTE: IE does not support multiply - For this you need to manually iterate through the pixels and multiply each component with each other.
You can test for support this way:
ctx.globalCompositeOperation = "multiply";
if (ctx.globalCompositeOperation === "multiply") {
// blend as above
}
else {
// iterate and blend manually
}
Blending mode luminosity is mentioned in comments and this can be used too of course. I just want to point a couple of things to consider. The first is that this is a non-separable blending mode meaning it depends on all components as it goes through the HSL color model. This makes it a bit more compute intensive.
The second is that if you end up having to do this manually (in for example IE) the code is a bit more complex to emulate, and will be noticeably slower.

In a 2D canvas, is there a way to give a sprite an outline?

I'd like to give a sprite an outline when the character gets healed/damaged/whatever but I can't think of a way to code this using the 2d canvas. If it were possible, I'd think it would be a global composite operation, but I can't think of a way to achieve it with one of them.
I did find this stackoverflow answer that recommends creating a fatter, solid color version of the original and put the original on top of it. That would give it an outline, but it seems like a lot of extra work especially considering I'm using placeholder art. Is there an easier way?
This question is different from the one linked because this is specifically about the HTML5 2D canvas. It may have a solution not available to the other question.
For what it's worth, I don't mind if the outline creates a wider border or keeps the sprite the same size, I just want the outline look.
Just draw your original image in 8 position around the original image
Change composite mode to source-in and fill with the outline color
Change composite mode back to source-over and draw in the original image at correct location
This will create a clean sharp outline with equal border thickness on every side. It is not so suited for thick outlines however. Image drawing is fast, especially when image is not scaled so performance is not an issues unless you need to draw a bunch (which in that case you would cache the drawings or use a sprite-sheet anyways).
Example:
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 2, // scale
i = 0, // iterator
x = 5, // final position
y = 5;
// draw images at offsets from the array scaled by s
for(; i < dArr.length; i += 2)
ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
// fill with color
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0,0,canvas.width, canvas.height);
// draw original image in normal mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
Maybe it would be worth trying this :
• build a canvas 1.1 time bigger than the original sprite
• fill it with the outline color
• draw the sprite scaled by 1.1 on the canvas using destination-in globalCompositeOperation.
Then you have a bigger 'shadow' of your sprite in the outline color.
When you want to draw the outline :
• draw the 'shadow' (centered)
• draw your sprite within the shadow.
Depending on the convexity of your sprite, this will work more or less nicely, but i think it's worth trying since it avoids you doubling the number of input graphic files.
I just did a short try as proof-of-concept and it quite works :
http://jsbin.com/dogoroxelupo/1/edit?js,output
Before :
After :
html
<html>
<body>
<image src='http://www.gifwave.com/media/463554/cartoons-comics-video-games-sprites-scott-pilgrim-paul-robertson_200s.gif' id='spr'></image>
<canvas id='cv' width = 500 height= 500 ></canvas>
</body>
</html>
code
window.onload=function() {
var spr = document.getElementById('spr');
var margin = 4;
var gh = createGhost(spr, '#F80', margin);
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
var outlined = true;
setInterval(function() {
ctx.clearRect(0,0,cv.width, cv.height);
if (outlined)
ctx.drawImage(gh, 0, 0)
ctx.drawImage(spr, 0, 0)
outlined = !outlined;
}, 400);
}
function createGhost (img, color, margin) {
var cv= document.createElement('canvas');
cv.width = img.width+2*margin;
cv.height = img.height + 2*margin;
var ctx = cv.getContext('2d');
ctx.fillStyle = color;
ctx.fillRect(0,0, cv.width, cv.height);
ctx.save();
ctx.globalCompositeOperation = 'destination-in';
var scale = cv.width/spr.width;
ctx.scale(cv.width/spr.width, cv.height/spr.height);
ctx.drawImage(img, -margin, -margin);
ctx.restore();
return cv;
}
You could use strokeRect method to outline the sprite after drawing it. It should be asy if you know your sprite's dimensions...

Categories

Resources