fillText on canvas keeps going behind the background - javascript

I'm using ctx.globalCompositeOperation = 'destination-over'. Though the text still goes behind.
Game.setup();
ctx = Game.context;
if (AllImagesLoaded === true) {
loading = false;
clearInterval(int);
BackgroundsA.push(true);
ctx.globalCompositeOperation = 'destination-over';
Interact = new text(window.innerWidth / 2, window.innerHeight / 2 - 200, 'Press F to Interact', 'white', '30px Verdana');
Sprites.push(new sprite(window.innerWidth / 2 - 275, window.innerHeight / 2 - 315, 'idle_jack/frame_1.PNG', 600, 600, 5));
Player = new component(window.innerWidth / 2, window.innerHeight / 2 - 100, 'rgba(0, 0, 0, 0)', 50, 150);
ctx.globalCompositeOperation = 'source-over';
Backgrounds.push(new background(0, 0, 'Backgrounds/Test/test2.jpg'));
ctx.globalCompositeOperation = 'destination-over';
The "new [KEYWORDS]" are functions for filling or drawing text/components/images.
Please help!

Why don't you draw everything from behind, i.e. firstly draw the backgrounds of the game, then text and at the end player and all of the game objects ?
However if you do want to draw everything like you're trying to now, then try changing global composite operation just before drawing, it's not clear if new Background() will actually draw the background or not from your code. Here is an article to read about global composite operation:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

Related

How to fill a shape on mouseover using canvas?

I am trying to make a bowl shape and its content would gradually show as we go over it from bottom to top (and vice versa). here is my code for the bowl and the static filling.
I am new to canvas and to js animation.
Does anyone have an idea and can help me? Thank you :)
const canvas = document.createElement('canvas')
document.body.appendChild(canvas)
canvas.width = 800
canvas.height = 500
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, 800, 500)
const bowl = new Path2D()
bowl.arc(400, 390, 100, 0, Math.PI)
ctx.stroke(bowl)
ctx.moveTo(300,390)
ctx.lineTo(500,390)
ctx.stroke()
const fluid = new Path2D()
fluid.arc(400, 391, 99, 0, Math.PI)
ctx.fillStyle = 'red'
ctx.fill(fluid)
As I know, you can't do this directly on the item. I mean that you can't add event on the bowl because canvas retrieve the x and y coordinates and "draw" a perpendicular line to the shape to check if there are in the same axis.
Please see this post here How do I add a simple onClick event handler to a canvas element?
Think about use "over" event, because it doesn't work on touch devices.

Html canvas zoom to specific region

I have Html canvas code that draws the following graph. Is there an easy way of zooming the area that is outlined with red rectangle without rewriting the code? This area is always top right quarter of the graph and I also would like to leave some space for the axis.
So, I found the solution:
Draw two times bigger original canvas
Retrieve and put the region image on new canvas:
var imgData = ctx.getImageData(canvas.width/2 - 20, 0, canvas.width/2 + 20, canvas.width/2 + 20);
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width /= 2
canvas.height /= 2
canvas.height += 20
ctx.putImageData(imgData, 0, 0);

putImageData of only portion of image data object

I have an ImageData object which holds the screenshot of all monitors, so it is one huge ImageData. I want to now draw one monitor per time. I have all the monitor dimensions.
So I'm trying to use ctx.putImageData(myImgDat, topLeftMonX, topLeftMonY, monWidth, monHeight) but its not working, I dont think I understand the dirty concept so well as seen in docs: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData#Understanding_putImageData
Is it possible to draw portion of imgaedata to canvas?
Yes, it's possible, it doesn't work on your code is because your arugments passed is not met the format of the function.
As MDN CanvasRenderingContext2D.putImageData() describes, it accepts 3 or 7 arguments, you have to pass either 3 or 7, otherwise, in your example, monWidth and monHeight will be used as dirtyX and dirtyY instead of dirtyWidth and dirtyHeighy. You code would probably do
Copy the rect(monWidth, monHeight, IMAGEDATA_WIDTH, IMAGEDATA_HEIGHT).
Put it on canvas's rect(topLeftMonX + monWidth, topLeftMonY + monHeight, IMAGEDATA_WIDTH, IMAGEDATA_HEIGHT).
So, and it's somehow not very straight to put the target region to (0,0) of the target canvas, to achieve by your give condition, you may have to do:
First move imageData's (0,0) to target canvas's (-topLeftMonX,
-topLeftMonY).
Then start to put imageData at imageData's (topLeftMonX, topLeftMonY) which now is at the position (0, 0) of canvas.
The rectangle size will be topLeftMonX x topLeftMonY.
ctx.putImageData(myImgDat,-topLeftMonX, -topLeftMonY, topLeftMonX, topLeftMonY, monWidth, monHeight);
Code above will copy the rect(topLeftMonX, topLeftMonY, monWidth, monHeight) on myImgDat to: rect(0, 0, monWidth, monHeight) on canvas.
You can take a look at how it works from the snippet below.
var canvas = document.getElementById('bigCanvas')
,ctx = canvas.getContext('2d');
var tcanvas = document.getElementById('testCanvas')
,tctx = tcanvas.getContext('2d');
var grd = tctx.createRadialGradient(150, 100, 10, 150, 110, 150);
grd.addColorStop(0, "black");
grd.addColorStop(0.15, "blue");
grd.addColorStop(0.3, "cyan");
grd.addColorStop(0.5, "green");
grd.addColorStop(0.7, "yellow");
grd.addColorStop(0.85, "orange");
grd.addColorStop(1, "red");
tctx.fillStyle = grd;
tctx.fillRect(0, 0, 300, 200);
var imageData = tctx.getImageData(0, 0, 300, 200);
// Move imagedata's origin to (-150, -100) on canvas,
// start to put data on canvas at imgae data's (150, 100) and size is 150x100
// => copy rect(150, 100, 150, 100) to canvas' s rect (0, 0, 150, 100)
ctx.putImageData(imageData, -150, -100, 150, 100, 150, 100);
// Move imagedata's origin to (150, 100) on canvas,
//start to put data on canvas at imgae data's (0, 0) and and size is 150x100
// => copy rect(0, 0, 150, 100) to canvas' s rect (150, 100, 150, 100)
ctx.putImageData(imageData, 150, 100, 0, 0, 150, 100);
// Move imagedata's origin to (150, -100) on canvas,
// start to put data on canvas at imgae data's (0, 100) and size is 150x100
// => copy rect(0, 100, 150, 100) to canvas' s rect (150, 0, 150, 100)
ctx.putImageData(imageData, 150, -100, 0, 100, 150, 100);
// Move imagedata's origin to (-150, 100) on canvas,
// start to put data on canvas at imgae data's (200, 0) and size is 100x100
// => copy rect(200, 0, 150, 100) to canvas' s rect (50, 100, 150, 100)
ctx.putImageData(imageData, -150, 100, 200, 0, 100, 100);
<div>Canvas for put ImageData:</div>
<canvas id="bigCanvas" width="300" height="200"></canvas>
<hr/>
<div>Canvas for get ImageData:</div>
<canvas id="testCanvas" width="300" height="200"></canvas>
This is another solution:
var iref = imagedata.data;
// start - because took a single screenshot of alllll put togather, lets portion out the imagedata
console.time('portion out image data');
for (var i=0; i<collMonInfos.length; i++) {
var screenUseW = collMonInfos[i].w;
var screenUseH = collMonInfos[i].h;
var screnImagedata = new ImageData(screenUseW, screenUseH);
var siref = screnImagedata.data;
var si = 0;
for (var y=collMonInfos[i].y; y<collMonInfos[i].y+screenUseH; y++) {
for (var x=collMonInfos[i].x; x<collMonInfos[i].x+screenUseW; x++) {
var pix1 = (fullWidth*y*4) + (x * 4);
var B = iref[pix1];
siref[si] = iref[pix1+2];
siref[si+1] = iref[pix1+1];
siref[si+2] = B;
siref[si+3] = 255;
si += 4;
}
}
collMonInfos[i].screenshot = screnImagedata;
}
console.timeEnd('portion out image data');

Display bottom canvas with a shadow effect - shadow within clipped area

I`m new with canvas so thanks for your patience.
I wrote an engine that is creating 2 different layers in 2 canvas elements which are one over another. They contain some generated pictures, which aren`t important here.
I'm trying to create an effect which will display bottom layer when I move mouse over the top layer and click.
Something like this:
This is what I have tried so far:
To use transparency on canvas element and display bottom canvas (fast but not usable)
Re-create a clipping region.
Whenever I press the mouse I store current coordinates and re-render the canvas with updated clipping region
Updating clipping region is slow if I use stroke to create shadows + I`m not sure how to remove lines from it (see picture).
If I remove shadow effect, it works really fast, but I need to have it.
The only thing that comes on my mind how to speed this, is to save coordinates of every click, and then to re-calculate that into 1 shape and drop a shadow on it - I`ll still have lines, but it will be faster because there won`t be thousand of circles to draw...
Any help will be most appreciated!
You can take advantage of the browser's built in interpolation by using it as a pseudo low-pass filter, but first by painting it black:
Copy the top layer to the bottom layer
Set source-in comp. mode
Draw all black
Set source-in comp. mode
Scale down image to 25%
Scale the 25% region back up to 50% of original (or double of current)
Scale the now 50% region back up to 100% of original. It will be blurred.
Depending on how much blur you want you can add additional steps. That being said: blurred shadow is an intensive operation no matter how it is twisted and turned. One can make compromise to only render the shadow on mouse up for example (as in the demo below).
Example
Example using two layers. Top layer let you draw anything, bottom will show shadow version at the bottom later while drawing.
var ctx = document.getElementById("top").getContext("2d"),
bctx = document.getElementById("bottom").getContext("2d"),
bg = new Image(),
isDown = false;
bg.src = "http://i.imgur.com/R2naCpK.png";
ctx.fillStyle = "#27f";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "destination-out"; // "eraser"
ctx.canvas.onmousedown = function(e) {isDown = true};
window.onmousemove = function(e) {
if (!isDown) return;
var pos = getPos(ctx.canvas, e);
ctx.beginPath();
ctx.moveTo(pos.x + 10, pos.y);
ctx.arc(pos.x, pos.y, 10, 0, 2*Math.PI); // erase while drawing
ctx.fill();
};
window.onmouseup = function(e) {
if (isDown) {
isDown = false;
makeShadow();
}
};
function makeShadow(){
var w = bctx.canvas.width,
h = bctx.canvas.height,
offset = 7,
alpha = 0.75;
// reset alpha
bctx.globalAlpha = 1;
// normal comp mode to clear as it is faster than using "copy"
bctx.globalCompositeOperation = "source-over";
bctx.clearRect(0, 0, w, h);
// copy top-layer to bottom-layer
bctx.drawImage(ctx.canvas, 0, 0);
// comp. mode will only draw in to non-alpha pixels next
bctx.globalCompositeOperation = "source-in";
// black overlay
bctx.fillRect(0, 0, w, h);
// copy mode so we don't need an extra canvas
bctx.globalCompositeOperation = "copy";
// step 1: reduce to 50% (quality related - create more steps to increase blur/quality)
bctx.drawImage(bctx.canvas, 0, 0, w, h, 0, 0, w * 0.5, h * 0.5);
bctx.drawImage(bctx.canvas, 0, 0, w * 0.5, h * 0.5, 0, 0, w * 0.25, h * 0.25);
bctx.drawImage(bctx.canvas, 0, 0, w * 0.25, h * 0.25, 0, 0, w * 0.5, h * 0.5);
// shadow transparency
bctx.globalAlpha = alpha;
// step 2: draw back up to 100%, draw offset
bctx.drawImage(bctx.canvas, 0, 0, w * 0.5, h * 0.5, offset, offset, w, h);
// comp in background image
bctx.globalCompositeOperation = "destination-over";
bctx.drawImage(bg, 0, 0, w, h);
}
function getPos(canvas, e) {
var r = canvas.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top};
}
div {position:relative;border:1px solid #000;width:500px;height:500px}
canvas {position:absolute;left:0;top:0}
#bottom {background:#eee}
<div>
<canvas id="bottom" width=500 height=500></canvas>
<canvas id="top" width=500 height=500></canvas>
</div>

Is it possible to make a gradient-transparent/layer masking image using canvas?

I've been following the lessons about transparency and gradients on the Mozilla site: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors but I have not been able to figure this one out.
I know I can achieve these effects with a png image; however, in the program I am working on the gradient will change constantly according to where the image is moved.
Here's an example of the effect I'm looking for.
http://home.insightbb.com/~epyonxl1/gradientex.jpg
Its possible to use context.globalCompositeOperation to make the the mask
context.drawImage(img, 0, 0, img.width, img.height, 0,0, img.width, img.height);
context.globalCompositeOperation = "destination-out";
gradient = context.createLinearGradient(0, 0, 0, img.height);
gradient.addColorStop(0, "rgba(255, 255, 255, 0.5)");
gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
context.fillStyle = gradient;
context.fillRect(0, 0, img.width, img.height);
This do not do per pixel manipulation and should be faster
To correctly merge two images using a transparency mask it's first necessary to take one of the two images and put it into an off screen canvas, and add the desired transparency mask using context.globalCompositeOperation = destination-out per #Tommyka's answer
var offscreen = document.createElement('canvas'); // detached from DOM
var context = offscreen.getContext('2d');
context.drawImage(image1, 0, 0, image1.width, image1.height);
var gradient = context.createLinearGradient(0, 0, 0, img.height);
gradient.addColorStop(0, "rgba(255, 255, 255, 0.5)");
gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
context.globalCompositeOperation = "destination-out";
context.fillStyle = gradient;
context.fillRect(0, 0, image1.width, image1.height);
Then, to actually merge the two images you then need to draw the other image into another canvas, and then simply draw the alpha-composited offscreen canvas on top of that:
var onscreen = document.getElementById('mycanvas');
var context2 = onscreen.getContext('2d');
context2.drawImage(image2, 0, 0, image2.width, image2.height);
context2.drawImage(offscreen, 0, 0, onscreen.width, onscreen.height);
Demo at http://jsfiddle.net/alnitak/rfdjoh31/4/
I've aded some code here: https://code.google.com/archive/p/canvasimagegradient/ that adds a drawImageGradient function to the CanvasRenderingContext2D. You can draw an image with a linear or radial gradient. It doesn't work in IE, even with excanvas, due to the lack of getImageData/putImageData support.
The following code for example will draw an image with a radial gradient (context retrieve and image load not shown):
var radGrad = ctx.createRadialGradient(
img.width / 2, img.height / 2, 10,
img.width / 2, img.height / 2, img.width/2);
radGrad.addColorStop(0, "transparent");
radGrad.addColorStop(1, "#000");
ctx.drawImageGradient(img, 112.5, 130, radGrad);
The code works as follows:
Create a canvas element dynamically and draw the image on it.
Retrieve the imageData for this new canvas.
Retrieve the imageData for the location on the canvas you want to draw the image on to.
Iterate through the destination imageData and update each pixel adding together a percentage (derived from the gradient transparency value) of the image and destination pixel values.
Finally put the updated image data back onto the destination canvas.
Obviously performance is an issue as images get larger. The image on https://code.google.com/archive/p/canvasimagegradient/ it takes about 6-10ms to draw. A 1024x768 image takes about 100ms-250ms to draw. Still usable though as long as you're not animating.
If you need to make an image transparent set the ctx.globalAlpha to whatever you need (1, no transparency, is default). Then reset it after you draw your image. This URL probably will help as well https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing.
I have modernized Alnitak's response by incorporating asynchronous loading of the images.
I also got rid of magic numbers.
const imageUrls = [
// Base image
'http://www.netstate.com/states/symb/flowers/images/apple_blossoms.jpg',
// Gradient image
'http://www.netstate.com/states/symb/flowers/images/oklahoma_rose.jpg'
];
const main = async () => {
const ctx = document.getElementById('cv').getContext('2d'),
baseImg = await loadImage(imageUrls[0]),
gradImg = await loadImage(imageUrls[1]);
draw(ctx, baseImg, gradImg);
};
/**
* Loads an Image object via a Promise.
* #param {String} url - Location of an image file
* #return {Promise<Image>} Returns a promise that resolves to an Image.
*/
const loadImage = (url) => new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', reject);
img.src = url;
});
/**
* Draws an image, as a gradient applied to it, above another image.
* #param {Image} baseImg - Image that is applied under the gradient
* #param {Image} gradImg - Image to be applied as a gradient
*/
const draw = (ctx, baseImg, gradImg) => {
const {width, height} = baseImg,
originX = Math.floor(width / 2),
originY = Math.floor(height / 2),
radius = Math.min(originX, originY);
const offScreen = document.createElement('canvas');
offScreen.width = offScreen.height = width;
const ctx2 = offScreen.getContext('2d');
ctx2.drawImage(gradImg, 0, 0, width, height);
const gradient = ctx2.createRadialGradient(originX, originY, 0, originX, originY, radius);
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 1)');
ctx2.fillStyle = gradient;
ctx2.globalCompositeOperation = 'destination-out';
ctx2.fillRect(0, 0, width, height);
ctx.drawImage(baseImg, 0, 0, width, height);
ctx.drawImage(offScreen, 0, 0, width, height);
};
main();
<canvas id="cv" width="300" height="300"></canvas>

Categories

Resources