I've seen this question before but the answers given are for canvas images that have been drawn on via path however, i'm drawing an image.
Is it possible to create an inset-shadow?
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 10;
context.shadowColor = 'rgba(30,30,30, 0.4)';
var imgOne = new Image();
imgOne.onload = function() {
context.drawImage(imgOne, 0, 0);
};
imgOne.src = "./public/circle.png";
So I draw the circle picture on. I've now at the moment got a slight shadow on the outside of the circle, how can I get this inset instead of offset?
Composition chain
Use a series of composite + draw operation to obtain inset shadow.
Note: the solution require exclusive access to the canvas element when created so either do this on an off-screen canvas and draw back to main, or if possible, plan secondary graphics to be drawn after this has been generated.
The needed steps:
Draw in original image
Invert alpha channel filling the canvas with a solid using xor composition
Define shadow and draw itself back in
Deactivate shadow and draw in original image (destination-atop)
var ctx = c.getContext("2d"), img = new Image;
img.onload = function() {
// draw in image to main canvas
ctx.drawImage(this, 0, 0);
// invert alpha channel
ctx.globalCompositeOperation = "xor";
ctx.fillRect(0, 0, c.width, c.height);
// draw itself again using drop-shadow filter
ctx.shadowBlur = 7*2; // use double of what is in CSS filter (Chrome x4)
ctx.shadowOffsetX = ctx.shadowOffsetY = 5;
ctx.shadowColor = "#000";
ctx.drawImage(c, 0, 0);
// draw original image with background mixed on top
ctx.globalCompositeOperation = "destination-atop";
ctx.shadowColor = "transparent"; // remove shadow !
ctx.drawImage(this, 0, 0);
}
img.src = "http://i.imgur.com/Qrfga2b.png";
<canvas id=c height=300></canvas>
Canvas will shadow where an image changes from opaque to transparent so, as K3N shows in his correct answer, you can turn the image inside out (opaque becomes transparent & visa-versa) so the shadows are drawn inside the circle.
If you know your circle's centerpoint and radius, you can use a stroked-path to create an inset circle shadow. Here's an example:
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
context.beginPath();
context.arc(cw/2,ch/2,75,0,Math.PI*2);
context.fillStyle='lightcyan';
context.fill();
context.globalCompositeOperation='source-atop';
context.shadowOffsetX = 500;
context.shadowOffsetY = 0;
context.shadowBlur = 15;
context.shadowColor = 'rgba(30,30,30,1)';
context.beginPath();
context.arc(cw/2-500,ch/2,75,0,Math.PI*2);
context.stroke();
context.stroke();
context.stroke();
context.globalCompositeOperation='source-over';
<canvas id="canvas" width=300 height=300></canvas>
If your path is irregular or hard to define mathematically, you can also use edge-path detection algorithms. One common edge-path algorithm is Marching Squares. Stackoverflow's K3N has coded a nice Marching Squares algorithm.
Inspired by markE's answer , I made my own version based on a png instead of vector-graphics.
Additionnaly, I made possible to choose the true alpha of the shadow (because the default shadow strength is a way too soft in my opinion)
var img = document.getElementById("myImage");
img.onload = function(){
createInnerShadow(this,5,1);
}
function createInnerShadow(img,distance,alpha){
//the size of the shadow depends on the size of the target,
//then I will create extra "walls" around the picture to be sure
//tbat the shadow will be correctly filled (with the same intensity everywhere)
//(it's not obvious with this image, but it is when there is no space at all between the image and its border)
var offset = 50 + distance;
var hole = document.createElement("canvas");
var holeContext = hole.getContext("2d");
hole.width = img.width + offset*2;
hole.height = img.height + offset*2;
//first, I draw a big black rect
holeContext.fillStyle = "#000000";
holeContext.fillRect(0,0,hole.width,hole.height);
//then I use the image to make an hole in it
holeContext.globalCompositeOperation = "destination-out";
holeContext.drawImage(img,offset,offset);
//I create a new canvas that will contains the shadow of the hole only
var shadow = document.createElement("canvas");
var shadowContext = shadow.getContext("2d");
shadow.width = img.width;
shadow.height = img.height;
shadowContext.filter = "drop-shadow(0px 0px "+distance+"px #000000 ) ";
shadowContext.drawImage(hole,-offset,-offset);
shadowContext.globalCompositeOperation = "destination-out";
shadowContext.drawImage(hole,-offset,-offset);
//now, because the default-shadow filter is really to soft, I normalize the shadow
//then I will be sure that the alpha-gradient of the shadow will start at "alpha" and end at 0
normalizeAlphaShadow(shadow,alpha);
//Finally, I create another canvas that will contain the image and the shadow over it
var result = document.createElement("canvas");
result.width = img.width;
result.height = img.height;
var context = result.getContext("2d");
context.drawImage(img,0,0)
context.drawImage(shadow,0,0);
//and that's it !
document.body.appendChild(result);
}
function normalizeAlphaShadow(canvas,alpha){
var imageData = canvas.getContext("2d").getImageData(0,0,canvas.width,canvas.height);
var pixelData = imageData.data;
var i,len = pixelData.length;
var max = 0;
for(i=3;i<len;i+=4) if(pixelData[i]>max) max = pixelData[i];
max = (255/max) * alpha;
for(i=3;i<len;i+=4) pixelData[i] *= max;
canvas.getContext("2d").putImageData(imageData,0,0)
}
<html>
<body>
<img id="myImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAACWCAYAAAB92c4YAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkFENzg2NTc4MDg5MTFFOEI1OTdBNEZCMEY2OTg3OTAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkFENzg2NTg4MDg5MTFFOEI1OTdBNEZCMEY2OTg3OTAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyQUQ3ODY1NTgwODkxMUU4QjU5N0E0RkIwRjY5ODc5MCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyQUQ3ODY1NjgwODkxMUU4QjU5N0E0RkIwRjY5ODc5MCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pk/K8voAADFpSURBVHja5H0HmFXV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" />
</body>
</html>
the jsfiddle is here : https://jsfiddle.net/jrekw5og/141/
Inspired by K3N's answer, I've created Inset.js for this exact situation!
Inset.js
Only requires setting ctx.shadowInset = true;
For example: http://codepen.io/patlillis/pen/ryoWey
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function() {
ctx.shadowInset = true;
ctx.shadowBlur = 25;
ctx.shadowColor = "#000";
ctx.drawImage(this, 0, 0);
}
img.src = "http://i.imgur.com/Qrfga2b.png";
const width = 100 * devicePixelRatio;
const height = 100 * devicePixelRatio;
// original canvas
const c = document.getElementById('canvas');
c.width = 300 * devicePixelRatio;
c.height = 300 * devicePixelRatio;
c.style.width = '300px';
c.style.height = '300px';
const cctx = c.getContext('2d');
cctx.fillStyle = 'rgb(20,205,75)';
cctx.arc(150 * devicePixelRatio, 150 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
cctx.fill();
// temporary canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width / devicePixelRatio}px`;
canvas.style.height = `${height / devicePixelRatio}px`;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
// original object on temporary canvas
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow cutting
ctx.globalCompositeOperation = 'xor';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow props
ctx.shadowBlur = 50;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = -25;
ctx.shadowColor = '#000';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow color
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// object cutting
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow opacity
cctx.globalAlpha = .4
// inserting shadow into original canvas
cctx.drawImage(canvas, 200, 200);
Colored shadow /w opacity
Related
I would like to create an isometric 3D cube with fillRect whose 3 faces have the same dimensions as the image below:
Edit: I want to do it with fillRect. The reason for this is that I will draw images on the 3 faces of the cube afterwards. This will be very easy to do since I will use exactly the same transformations as for drawing the faces.
Edit 2: I didn't specify that I want to avoid using an external library so that the code is as optimized as possible. I know that it is possible to calculate the 3 matrices beforehand to draw the 3 faces and make a perfect isometric cube.
Edit 3: As my example code showed, I want to be able to set the size of the side of the isometric cube on the fly (const faceSize = 150).
I have a beginning of code but I have several problems:
The faces are not all the same dimensions
I don't know how to draw the top face
const faceSize = 150;
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Top Face (not big enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.scale(1, .5);
ctx.rotate(-45 * Math.PI / 180);
ctx.fillStyle = 'yellow';
ctx.fillRect(0, -faceSize, faceSize, faceSize);
ctx.restore();
// Left Face (not high enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.transform(1, .5, 0, 1, 0, 0);
ctx.fillStyle = 'red';
ctx.fillRect(-faceSize, 0, faceSize, faceSize);
ctx.restore();
// Right Face (not high enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.transform(1, -.5, 0, 1, 0, 0);
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, faceSize, faceSize);
ctx.restore();
<canvas width="400" height="400"></canvas>
I used a large part of #enhzflep's code which I adapted so that the width of the cube is dynamically changeable.
All the code seems mathematically correct, I just have a doubt about the value 1.22 given as a parameter to scaleSelf. Why was this precise value chosen?
Here is the code:
window.addEventListener('load', onLoad, false);
const canvas = document.createElement('canvas');
function onLoad() {
//canvas.width = cubeWidth;
//canvas.height = faceSize * 2;
canvas.width = 400;
canvas.height = 400;
document.body.appendChild(canvas);
drawCube(canvas);
}
function drawCube() {
const scale = Math.abs(Math.sin(Date.now() / 1000) * canvas.width / 200); // scale effect
const faceSize = 100 * scale;
const radians = 30 * Math.PI / 180;
const cubeWidth = faceSize * Math.cos(radians) * 2;
const centerPosition = {
x: canvas.width / 2,
y: canvas.height / 2
};
const ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const defaultMat = [1, 0, 0, 1, 0, 0];
// Left (red) side
const leftMat = new DOMMatrix(defaultMat);
leftMat.translateSelf(centerPosition.x - cubeWidth / 2, centerPosition.y - faceSize / 2);
leftMat.skewYSelf(30);
ctx.setTransform(leftMat);
ctx.fillStyle = '#F00';
ctx.fillRect(0, 0, cubeWidth / 2, faceSize);
// Right (blue) side
const rightMat = new DOMMatrix(defaultMat);
rightMat.translateSelf(centerPosition.x, centerPosition.y);
rightMat.skewYSelf(-30);
ctx.setTransform(rightMat);
ctx.fillStyle = '#00F';
ctx.fillRect(0, 0, cubeWidth / 2, faceSize);
// Top (yellow) side
const topMat = new DOMMatrix(defaultMat);
const toOriginMat = new DOMMatrix(defaultMat);
const fromOriginMat = new DOMMatrix(defaultMat);
const rotMat = new DOMMatrix(defaultMat);
const scaleMat = new DOMMatrix(defaultMat);
toOriginMat.translateSelf(-faceSize / 2, -faceSize / 2);
fromOriginMat.translateSelf(centerPosition.x, centerPosition.y - faceSize / 2);
rotMat.rotateSelf(0, 0, -45);
scaleMat.scaleSelf(1.22, (faceSize / cubeWidth) * 1.22);
topMat.preMultiplySelf(toOriginMat);
topMat.preMultiplySelf(rotMat);
topMat.preMultiplySelf(scaleMat);
topMat.preMultiplySelf(fromOriginMat);
ctx.setTransform(topMat);
ctx.fillStyle = '#FF0';
ctx.fillRect(0, 0, faceSize, faceSize);
ctx.restore();
requestAnimationFrame(drawCube);
}
Here's a quick n dirty approach to the problem. It's too hot here for me to really think very clearly about this question. (I struggle with matrix maths too)
There's 2 things I think worth mentioning, each of which has an effect on the scaling operation.
width and height of the finished figure (and your posted example image) are different.
I think it's the ratio of the distance between (opposite) corners of the untransformed rectangle which fills 1/4 of the canvas, and the finished yellow side which affect the scaling.
Also, note that I'm drawing a square of canvas.height/2 sidelength for the yellow side, whereas I was drawing a rectangle for the red and blue sides.
In the scaling section, width/4 and height/4 are both shorthand for (width/2)/2 and (height/2)/2. width/2 and height/2 give you a rectangle filling 1/2 of the canvas, with a centre (middle of the square) located at (width/2)/2, (height/2)/2 - height/4 means something different in the translation section (even though it's the same number)
With that said, here's the sort of thing I was talking about earlier.
"use strict";
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
let width = 147;
let height = 171;
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
document.body.appendChild(canvas);
drawIsoDemo(canvas);
}
function drawIsoDemo(destCanvas)
{
let ctx = destCanvas.getContext('2d');
let width = destCanvas.width;
let height = destCanvas.height;
ctx.fillStyle = '#000';
ctx.fillRect(0,0,width,height);
var idMatVars = [1,0, 0,1, 0,0];
// left (red) side
let leftMat = new DOMMatrix( idMatVars );
leftMat.translateSelf( 0, 0.25*height );
leftMat.skewYSelf(30);
ctx.save();
ctx.transform( leftMat.a, leftMat.b, leftMat.c, leftMat.d, leftMat.e, leftMat.f);
ctx.fillStyle = '#F00';
ctx.fillRect(0,0,width/2,height/2);
ctx.restore();
// right (blue) side
let rightMat = new DOMMatrix( idMatVars );
rightMat.translateSelf( 0.5*width, 0.5*height );
rightMat.skewYSelf(-30);
ctx.save();
ctx.transform( rightMat.a, rightMat.b, rightMat.c, rightMat.d, rightMat.e, rightMat.f);
ctx.fillStyle = '#00F';
ctx.fillRect(0,0,width/2,height/2);
ctx.restore();
// top (yellow) side
let topMat = new DOMMatrix( idMatVars );
let toOriginMat = new DOMMatrix( idMatVars );
let fromOriginMat = new DOMMatrix(idMatVars);
let rotMat = new DOMMatrix(idMatVars);
let scaleMat = new DOMMatrix(idMatVars);
toOriginMat.translateSelf(-height/4, -height/4);
fromOriginMat.translateSelf(width/2,height/4);
rotMat.rotateSelf(0,0,-45);
scaleMat.scaleSelf(1.22,((height/2)/width)*1.22);
topMat.preMultiplySelf(toOriginMat);
topMat.preMultiplySelf(rotMat);
topMat.preMultiplySelf(scaleMat);
topMat.preMultiplySelf(fromOriginMat);
ctx.save();
ctx.transform( topMat.a, topMat.b, topMat.c, topMat.d, topMat.e, topMat.f);
ctx.fillStyle = '#FF0';
ctx.fillRect(0,0,height/2,height/2);
ctx.restore();
}
If we overlay a circle on your isometric cube, we can see that the outer vertices are spaced equally apart. In fact it's always 60°, which is no wonder as it's a hexagon.
So all we have to do is obtaining the coordinates for the outer vertices. This is quite easy as we can make a further assumption: if you look at the shape again, you'll notice that the length of each of the cube's sides seems to be the radius of the circle.
With the help of a little trigonometry and a for-loop which increments by 60 degrees, we can put calculate and put all those vertices into an array and finally connect those vertices to draw the cube.
Here's an example:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawCube(x, y, sideLength) {
let vertices = [new Point(x, y)];
for (let a = 0; a < 6; a++) {
vertices.push(new Point(x + Math.cos(((a * 60) - 30) * Math.PI / 180) * sideLength, y + Math.sin(((a * 60) - 30) * Math.PI / 180) * sideLength));
}
ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
ctx.lineTo(vertices[5].x, vertices[5].y);
ctx.lineTo(vertices[6].x, vertices[6].y);
ctx.lineTo(vertices[1].x, vertices[1].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fill();
ctx.fillStyle = "#a0a0a0";
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
ctx.lineTo(vertices[1].x, vertices[1].y);
ctx.lineTo(vertices[2].x, vertices[2].y);
ctx.lineTo(vertices[3].x, vertices[3].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fill();
ctx.fillStyle = "#efefef";
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
ctx.lineTo(vertices[3].x, vertices[3].y);
ctx.lineTo(vertices[4].x, vertices[4].y);
ctx.lineTo(vertices[5].x, vertices[5].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fill();
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
drawCube(200, 150, 85);
canvas {
background: #401fc1;
}
<canvas id="canvas" width="400" height="300"></canvas>
EDIT
What you want to achieve is ain't that easily simply because the CanvasRenderingContext2D API actually does not offer a skewing/shearing transform.
Nevertheless with the help of a third-party library we're able to transform the three sides in an orthographic way. It's called perspective.js
Still we need to calculate the outer vertices but instead of using the moveTo/lineTo commands, we forward the coordinates to perspective.js to actually do the perspective distortion of some source images.
Here's another example:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
function drawCube(x, y, sideLength) {
let vertices = [new Point(x, y)];
for (let a = 0; a < 6; a++) {
vertices.push(new Point(x + Math.cos(((a * 60) - 30) * Math.PI / 180) * sideLength, y + Math.sin(((a * 60) - 30) * Math.PI / 180) * sideLength));
}
let p = new Perspective(ctx, images[0]);
p.draw([
[vertices[5].x, vertices[5].y],
[vertices[6].x, vertices[6].y],
[vertices[1].x, vertices[1].y],
[vertices[0].x, vertices[0].y]
]);
p = new Perspective(ctx, images[1]);
p.draw([
[vertices[0].x, vertices[0].y],
[vertices[1].x, vertices[1].y],
[vertices[2].x, vertices[2].y],
[vertices[3].x, vertices[3].y]
]);
p = new Perspective(ctx, images[2]);
p.draw([
[vertices[4].x, vertices[4].y],
[vertices[5].x, vertices[5].y],
[vertices[0].x, vertices[0].y],
[vertices[3].x, vertices[3].y]
]);
}
function loadImages(index) {
let image = new Image();
image.onload = function(e) {
images.push(e.target);
if (index + 1 < sources.length) {
loadImages(index + 1);
} else {
drawCube(200, 150, 125, e.target);
}
}
image.src = sources[index];
}
let sources = ["https://picsum.photos/id/1079/200/300", "https://picsum.photos/id/76/200/300", "https://picsum.photos/id/79/200/300"];
let images = [];
loadImages(0);
canvas {
background: #401fc1;
}
<script src="https://cdn.rawgit.com/wanadev/perspective.js/master/dist/perspective.min.js"></script>
<canvas id="canvas" width="400" height="300"></canvas>
Is it possible to add a color filter to a black and white image?
So for example, lets say I want to use this image as a particle in a game:particle image
Is it possible for me to draw this image with some sort of filter so it can become whatever color i'd like?
Similar to how you can color in a rectangle with whatever color you want:
ctx.fillstyle = "rgb(255, 0, 0)";
ctx.fillrect(0, 0, 100, 100);
is there any way for me to dynamically add a color filter over an image in this way?
(I only want to apply the color to the image not the whole canvas)
You can use your image as a mask. Use ctx.globalCompositeOperation after drawing the mask with "source-in" when you draw the background to "cut" after mask. And "destination-in" if your mask is drawn after the background.
let color = 'rgb(255, 0, 0)';
let image = new Image();
image.src = 'https://i.stack.imgur.com/XFpL3.png';
image.onload = () => {
let canvas = document.createElement('canvas');
let w = canvas.width = image.width;
let h = canvas.height = image.height;
let ctx = canvas.getContext('2d');
ctx.fillStyle = color;
ctx.drawImage(image,0,0);
ctx.globalCompositeOperation = 'source-in';
ctx.fillRect(0,0,w,h);
document.body.append(canvas);
}
Updated
I miss read the question and gave an answer that may not suit your needs. Thus this update demonstrates how to color BW images for particle FX.
Unfortunately the 2D API is not designed with game rendering in mind and for the best results you would implement the rendering via WebGL giving you a huge variety of FX and a massive performance gain. However WebGL requires a lot of code and a good understanding of shaders.
NOTE the image you provided is black. When coloring a black and white image the black will still be black. I will thus assume the image you want colored is white. I will be using a copy of your image that has set the Alpha range from 0 - 255 and the RGB have a circular gradient from black to white.
Sprites
In the game world an image rendered to the display is called a sprite. A sprite is a rectangular image, It has 4 channels RGBA and can be drawn at any position, rotation, scale, and fade (Alpha)
An example of a basic sprite rendering solution using the 2D API.
To color individual sprites you have two options.
Option 1 ctx.filter
You could use the ctx.filter property and create a custom filter to color sprites.
However this is not optimal as filters are rather slow when compared to alternative solutions.
Option 2 Split Channels
To draw a sprite in any color you will need to split the image into its red green blue and alpha parts. Then you render each channel depending on the color you want it to display.
To avoid security limits you can split the image using the following functions
Copy image
Creates a copy of the image as a canvas
function copyImage(img) { // img can be any image type
const can = Object.assign(document.createElement("canvas"), {
width: img.width,
height: img.height,
});
can.ctx = can.getContext("2d");
can.ctx.drawImage(img, 0, 0, img.width, img.height);
return can;
}
Filter channel
This will copy an image and remove two or three RGBA color channels. It requires the function copyImage.
function imageFilterChannel(img, channel = "red") {
const imgf = copyImage(img);
// remove unwanted channel data
imgf.ctx.globalCompositeOperation = "multiply";
imgf.ctx.fillStyle = imageFilterChannel.filters[channel] ?? "#FFF";
imgf.ctx.fillRect(0,0, img.width, img.height);
// add alpha mask
imgf.ctx.globalCompositeOperation = "destination-in";
imgf.ctx.drawImage(img, 0, 0, img.width, img.height);
imgf.ctx.globalCompositeOperation = "source-over";
return imgf;
}
imageFilterChannel.filters = {
red: "#F00",
green: "#0F0",
blue: "#00F",
alpha: "#000",
};
Colored sprite
This function will load an image, create the 4 separate channel images and encapsulate it in a sprite object.
It requires the function imageFilterChannel.
NOTE there is no error checking
function createColorSprite(srcUrl) {
var W, H;
const img = new Image;
img.src = srcUrl;
const channels = [];
img.addEventListener("load",() => {
channels[0] = imageFilterChannel(img, "red");
channels[1] = imageFilterChannel(img, "green");
channels[2] = imageFilterChannel(img, "blue");
channels[3] = imageFilterChannel(img, "alpha");
API.width = W = img.width;
API.height = H = img.height;
API.ready = true;
}, {once: true});
const API = {
ready: false,
drawColored(ctx, x, y, scale, rot, color) { // color is CSS hex color #RRGGBBAA
// eg #FFFFFFFF
// get RGBA from color string
const r = parseInt(color[1] + color[2], 16);
const g = parseInt(color[3] + color[4], 16);
const b = parseInt(color[5] + color[6], 16);
const a = parseInt(color[7] + color[8], 16);
// Setup location and transformation
const ax = Math.cos(rot) * scale;
const ay = Math.sin(rot) * scale;
ctx.setTransform(ax, ay, -ay, ax, x, y);
const offX = -W / 2;
const offY = -H / 2;
// draw alpha first then RGB
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = a / 255;
ctx.drawImage(channels[3], offX, offY, W, H);
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = r / 255;
ctx.drawImage(channels[0], offX, offY, W, H);
ctx.globalAlpha = g / 255;
ctx.drawImage(channels[1], offX, offY, W, H);
ctx.globalAlpha = b / 255;
ctx.drawImage(channels[2], offX, offY, W, H);
ctx.globalCompositeOperation = "source-over";
}
};
return API;
}
Used as follows
// to load
const image = createColorSprite("https://i.stack.imgur.com/RXAVJ.png");
// to render with red
if (image.ready) { image.drawColored(ctx, 100, 100, 1, 0, "#FF0000FF") }
DEMO
Using the functions above the demo shows how to use them and will give you some feedback in regards to the performance. It uses some of the code from the sprite example linked above.
Demo renders 200 images.
Note that there is some room for performance gains.
Don`t render channel image if that channel is zero
Don's use CSS color string, use an array of unit colors, saves the need to parse CSS color string. eg RGBA red as [1,0,0,1]
Use images that closely match the size they will be displayed, The images in the demo are many times larger than they are displayed.
function copyImage(img) { // img can be any image type
const can = Object.assign(document.createElement("canvas"), {
width: img.width,
height: img.height,
});
can.ctx = can.getContext("2d");
can.ctx.drawImage(img, 0, 0, img.width, img.height);
return can;
}
function imageFilterChannel(img, channel = "red") {
const imgf = copyImage(img);
// remove unwanted channel data
imgf.ctx.globalCompositeOperation = "multiply";
imgf.ctx.fillStyle = imageFilterChannel.filters[channel] ?? "#FFF";
imgf.ctx.fillRect(0,0, img.width, img.height);
// add alpha mask
imgf.ctx.globalCompositeOperation = "destination-in";
imgf.ctx.drawImage(img, 0, 0, img.width, img.height);
imgf.ctx.globalCompositeOperation = "source-over";
return imgf;
}
imageFilterChannel.filters = {
red: "#F00",
green: "#0F0",
blue: "#00F",
alpha: "#000",
};
function createColorSprite(srcUrl) {
var W, H;
const img = new Image;
img.src = srcUrl;
const channels = [];
img.addEventListener("load",() => {
channels[0] = imageFilterChannel(img, "red");
channels[1] = imageFilterChannel(img, "green");
channels[2] = imageFilterChannel(img, "blue");
channels[3] = imageFilterChannel(img, "alpha");
API.width = W = img.width;
API.height = H = img.height;
API.ready = true;
}, {once: true});
const API = {
ready: false,
drawColored(ctx, x, y, scale, rot, color) { // color is CSS hex color #RRGGBBAA
// eg #FFFFFFFF
// get RGBA from color string
const r = parseInt(color[1] + color[2], 16);
const g = parseInt(color[3] + color[4], 16);
const b = parseInt(color[5] + color[6], 16);
const a = parseInt(color[7] + color[8], 16);
// Setup location and transformation
const ax = Math.cos(rot) * scale;
const ay = Math.sin(rot) * scale;
ctx.setTransform(ax, ay, -ay, ax, x, y);
const offX = -W / 2;
const offY = -H / 2;
// draw alpha first then RGB
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = a / 255;
ctx.drawImage(channels[3], offX, offY, W, H);
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = r / 255;
ctx.drawImage(channels[0], offX, offY, W, H);
ctx.globalAlpha = g / 255;
ctx.drawImage(channels[1], offX, offY, W, H);
ctx.globalAlpha = b / 255;
ctx.drawImage(channels[2], offX, offY, W, H);
ctx.globalCompositeOperation = "source-over";
}
};
return API;
}
var image = createColorSprite("https://i.stack.imgur.com/C7qq2.png?s=328&g=1");
var image1 = createColorSprite("https://i.stack.imgur.com/RXAVJ.png");
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
var w,h;
function resize(){ w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
resize();
addEventListener("resize",resize);
function rand(min,max){return Math.random() * (max ?(max-min) : min) + (max ? min : 0) }
const randHex = () => (Math.random() * 255 | 0).toString(16).padStart(2,"0");
const randColRGB = () => "#" + randHex() + randHex() + randHex() + "FF";
function DO(count,callback){ while (count--) { callback(count) } }
const sprites = [];
DO(200,(i)=>{
sprites.push({
x : rand(w), y : rand(h),
xr : 0, yr : 0, // actual position of sprite
r : rand(Math.PI * 2),
scale: rand(0.1,0.25),
dx: rand(-2,2), dy : rand(-2,2),
dr: rand(-0.2,0.2),
color: randColRGB(),
img: i%2 ? image : image1,
});
});
function update(){
var ihM,iwM;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
if(image.ready && image1.ready){
for(var i = 0; i < sprites.length; i ++){
var spr = sprites[i];
iw = spr.img.width;
ih = spr.img.height;
spr.x += spr.dx;
spr.y += spr.dy;
spr.r += spr.dr;
iwM = iw * spr.scale * 2 + w;
ihM = ih * spr.scale * 2 + h;
spr.xr = ((spr.x % iwM) + iwM) % iwM - iw * spr.scale;
spr.yr = ((spr.y % ihM) + ihM) % ihM - ih * spr.scale;
spr.img.drawColored(ctx, spr.xr, spr.yr, spr.scale, spr.r, spr.color);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
Old answer
Adding color FXs
After the image is drawn set either ctx.globalCompositeOperation = "color", or ctx.globalCompositeOperation = "hue" and draw over the image in the color you want.
Example
ctx.drawImage(img, 0, 0, 50, 50); // draw image. Ensure that the current
// composite op is
// "source-over" see last line
ctx.globalCompositeOperation = "color"; // set the comp op (can also use "hue")
ctx.fillStyle = "red"; // set fillstyle to color you want
ctx.fillRect(0,0,50,50); // draw red over image
ctx.globalCompositeOperation = "source-over";// restore default comp op
For more info see MDN Global Composite Operations
You can try positioning a layer over the image with a partially transparent background:
.container{
position:relative;
width:fit-content;
}
.layer{
position:absolute;
height:100%;
width:100%;
background-color:rgb(0, 98, 255, 0.5);
z-index:1;
}
<div class="container">
<div class="layer"></div>
<img src="https://i.stack.imgur.com/XFpL3.png">
</div>
I am creating a game concept with this code: https://pastebin.com/pMNR1CfH. However, depending on the size of the window, more or less of the objects on screen will be shown. How can I resize it so the view remains the same?
I have these variables to try and scale the canvas, but I don't know where to place the canvas.scale function.
var scalewidth = window.innerWidth / 1920;
var scaleheight = window.innerHeight / 969;
thank you
I'll assume that you want to:
keep the aspect ratio of the graphics in tact (a circle should not be displayed as ellipse)
completely visualise the area (0,0)-(1920,969) being used to make the drawing
centre the graphics when there is spare room in one of the two dimensions of the view port.
Here is how you can do that:
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
window.addEventListener('resize', draw);
function draw() {
// Get current
let viewSizes = [canvas.width = window.innerWidth, canvas.height = window.innerHeight];
// Reset the canvas
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transformation
ctx.clearRect(0, 0, ...viewSizes); // wipe
// Determine scale factor
let worldSizes = [1920, 969];
let scales = worldSizes.map((worldSize, i) => viewSizes[i] / worldSize);
let minScale = Math.min(...scales);
// Determine shift in order to center the content
let shift = scales.map((scale, i) => (viewSizes[i] - minScale * worldSizes[i]) / 2);
ctx.setTransform(minScale, 0, 0, minScale, ...shift);
// Draw the contents using world coordinates (0,0)-(1920,969): This is demo
ctx.beginPath();
ctx.arc(960, 485, 484, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.rect(10, 455, 1900, 60);
ctx.fillStyle = "red";
ctx.fill();
ctx.stroke();
}
draw();
body { margin: 0; overflow: hidden }
<canvas></canvas>
I was trying to make two different shapes that are different colors but it isn't working. Both of the shapes are the same colors. Please help!(Please note that I am not the best coder in the world)
I've looked for other examples on this website, but all of them use the lineTo() method and I would like to use the rect() method just to make things easier.
//make canvas and set it up
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
canvas.style.position = 'absolute';
canvas.style.left = '0px';
canvas.style.top = '0px';
canvas.style.backgroundColor = '#D0C6C6';
var cH = canvas.height;
var cW = canvas.width;
//draw paddles
//variables
var paddleLength = 120;
var redPaddleY = window.innerHeight / 2;
var bluePaddleY = window.innerHeight / 2;
var paddleWidth = 20;
//drawing starts
function drawPaddles() {
//RED PADDLE
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
//BLUE PADDLE
var bluePaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12 * 14, bluePaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
redPaddle('red');
bluePaddle('blue');
};
var interval = setInterval(drawPaddles, 25);
Whenever you add a shape to the canvas it becomes part of the current path. The current path remains open until you tell the canvas to start a new one with beginPath(). This means that when you add your second rect() it is combined with the first and filled with the same colour.
The simplest fix would be to use the fillRect() function instead of rect which begins, closes and fills a path in one call.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.fillRect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
};
If you still want to use rect() you should tell the canvas to begin a new path for each paddle.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
I would also suggest moving the clearRect() outside of the drawing functions too. Clear once per frame and draw both paddles.
...
ctx.clearRect(0, 0, cW, cH);
redPaddle();
bluePaddle();
...
You should also investigate requestAnimationFrame() to do your animation loop as it provides many performance improvements over intervals.
I am trying to create a hole in canvas; and loading image in canvas after.
I want this canvas to contain a hole at a top layer.
I just read about counter-clockwise canvas rules and then created hole with counter clockwise position as below-
var c = document.getElementById("canvas-front");
var ctx = c.getContext("2d");
var centerX = c.width / 2;
var centerY = c.offsetTop;
var radius = 30;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI,true);//boolean true to counter-clockwise
ctx.fillStyle = 'black';
ctx.fill();
I have canvas before image in it-
After applying image in this canvas-
Canvas-
<canvas id="canvas-front" width="261" height="506"></canvas>
As you can see from both the images, I can't get this hole in canvas work.
How do I create this arc so that image doesn't overlap this.
I get this issue solved-
What I had to do-
On change of file I did empty to canvas and redrew image and then while image is being loaded
the arc is being created to counter-clockwise position-
Drawing the canvas again on image change-
var canvas = document.getElementById("canvas-front");
canvas.width = canvas.width;//blanks the canvas
var c = canvas.getContext("2d");
Creating image and giving it required src from changed input-
var img = new Image();
img.src = e.target.result;
Loading image and creating arc parallely-
img.onload = function () {
c.drawImage(img, 0, 0);
var centerX = canvas.width / 2;
var centerY = canvas.offsetTop;
var radius = 30;
c.beginPath();
c.arc(centerX, centerY, radius, 0, 2 * Math.PI, true);
c.fillStyle = 'black';
c.fill();
}