How to bring one canvas context over the top of another? - javascript

I am using two images in a canvas, now i want to bring one image over another, ie. i want to bring the plane over the sky, how can i do that?
here is my code
var canvas = document.getElementById('canvas');
var skyContext = canvas.getContext('2d');
var planeContext = canvas.getContext('2d');
var sky = new Image();
sky.src = './images/m35.jpeg';
sky.onload = function () {
skyContext.drawImage(sky, 0, 0, 250, 250, 50, 50, 250, 250);
}
var plane = new Image();
plane.src = './images/space-ship.png';
plane.onload = function () {
planeContext.drawImage(plane, 0, 0, 70, 80, 50, 250, 70, 80);
}
In the above code, the sky is coming at the front of the plane making the plane invisible.
I also tried to use the same context like this but i am not able to bring the image at the top of another.

The most effective way to do this (especially as it looks like you're creating a game) is to use two separate canvas elements, positioned on top of each other using CSS.
For example:
var canvasMain = document.getElementById('canvasMain');
var canvasBackground = document.getElementById('canvasBackground');
var skyContext = canvasMain.getContext('2d');
var sky = new Image();
sky.src = 'https://i.stack.imgur.com/xjj19.jpg';
sky.onload = function () {
skyContext.drawImage(sky, 0, 0, 250, 250, 50, 50, 250, 250);
}
var planeContext = canvasBackground.getContext('2d');
var plane = new Image();
plane.src = 'https://i.stack.imgur.com/nHugQ.png';
plane.onload = function () {
planeContext.drawImage(plane, 0, 0, 70, 80, 50, 250, 70, 80);
}
.canvas {
position: absolute;
top: 0;
left: 0;
}
<canvas id="canvasMain" class="canvas" width="700" height="500"></canvas>
<canvas id="canvasBackground" class="canvas" width="700" height="500"></canvas>

The issue with your code is that the images are drawn once loaded and I would suspect that because the sky image is larger than the plane image it takes longer to load so the sky image is being drawn second.
Drawing things on canvas works like layers in photoshop with whatever is drawn last overwriting things drawn before it.
It is possible to use the same canvas for both the sky and the plane, you just need to wait until both images have loaded then draw them in the correct order to the sky is drawn first, then the plane on top.
You can use the same canvas context for both objects if the are on the same canvas, no need to create multiple contexts (2 just point to the same place anyway). Usually when I work with canvases I just create one context variable called ctx as its a lot quicker than typing context.etc all the time.
almcd answer is one way to do it, though to me the background and main are reversed; I would put the sky / galaxy on the background and draw the plane on the main canvas, but there is a better way...
If the sky never changes, 2 canvases is not even needed, just one canvas with a background image behind it; the sky. When it comes time to animate the plane, this method of having the background a static image means there is less to draw each frame of the game loop, so you will get better FPS.
In terms of a solution for you, I think 1 canvas with the sky as a background image positioned by CSS, and only the plane being drawn on the canvas is the best, here is the code for that...
<!doctype HTML>
<html>
<head>
<style>
#canvasContainer {
background-image: url('https://i.stack.imgur.com/xjj19.jpg');
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div id="canvasContainer">
<canvas id="canvas" width="1200" height="600"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Load the plane and draw it.
var plane = new Image();
plane.src = 'https://i.stack.imgur.com/nHugQ.png';
plane.onload = function () {
ctx.drawImage(plane, 100, 100);
}
</script>
</body>
</html>
You can move the plane by changing the 100, 100 parameters sent to draw image, as these are the left and top used to position the plane image.
Kind regards,
DouG.
Creator of Winwheel.js a feature packed JavaScript library for making spinning prize wheels on HTML canvas. See http://dougtesting.net

Related

zoom into html canvas

I would like show a cutting of a canvas element in another canvas element. For explanation i have the following structure:
Canvas Element which gets filled it's background by an image. On top of this image i draw a arrow and a possible path. The background-image is really big, which means that i can not get that much Information from this big image. This is the reason for point two.
I would like to show a cutting of the canvas element 1. For example like the following image:
Currently i get the coordinate of the red arrow in canvas element 1, now i would like to do something like a cutting of this section with offset like in the image.
How could i solve something like this with JavaScript / JQuery. In summary i have two canvas elements. One of them is showing a big map with a red arrow which represents the current location (this works already), but now i wanna show a second canvas element with the zoom of this section where the red arrow is. Currently i am getting the coordinates, but no idea how i could "zoom" into an canvas element.
Like some of the current answers said, i provide some code:
My HTML Code, there is the mainCanvasMap, which has a Background Image and there is the zoomCanvas, which should display a section of the mainCanvasMap!
Here is a JavaScript snippet, which renders the red arrow on the map and should provide a zoom function (where the red-arrow is located) to the zoomCanvas Element.
var canvas = {}
canvas.canvas = null;
canvas.ctx = null;
canvas.scale = 0;
var zoomCanvas = {}
zoomCanvas.canvas = null;
zoomCanvas.ctx = null;
zoomCanvas.scale = 0;
$(document).ready(function () {
canvas.canvas = document.getElementById('mainCanvasMap');
canvas.ctx = canvas.canvas.getContext('2d');
zoomCanvas.canvas = document.getElementById('zoomCanvas');
zoomCanvas.ctx = zoomCanvas.canvas.getContext('2d');
setInterval(requestTheArrowPosition, 1000);
});
function requestTheArrowPosition() {
renderArrowOnMainCanvasElement();
renderZoomCanvas();
}
function renderArrowOnMainCanvasElement(){
//ADD ARROW TO MAP AND RENDER THEM
}
function renderZoomCanvas() {
//TRY TO ADD THE ZOOM FUNCTION, I WOULD LIKE TO COPY A SECTION OF THE MAINCANVASMAP
zoomCanvas.ctx.fillRect(0, 0, zoomCanvas.canvas.width, zoomCanvas.canvas.height);
zoomCanvas.ctx.drawImage(canvas.canvas, 50, 100, 200, 100, 0, 0, 400, 200);
zoomCanvas.canvas.style.top = 100 + 10 + "px"
zoomCanvas.canvas.style.left = 100 + 10 + "px"
zoomCanvas.canvas.style.display = "block";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<!--MY MAIN CANVAS ELEMENT, WHICH HAS A BACKGROUND IMAGE AND WHERE THE ARROW IS RENDEREED-->
<canvas id="mainCanvasMap" style="width:100%; height:100%; background: url('https://image.jimcdn.com/app/cms/image/transf/dimension=624x10000:format=jpg/path/s7d1eecaa5cee1012/image/i4484f962de0bf3c2/version/1543751018/image.jpg') 0% 0% / 100% 100%;"></canvas>
<!-- MY ZOOM CANVAS ELEMENT, SHOULD SHOW A CUTTING OF THE MAINCANVASMAP -->
<canvas id="zoomCanvas" style="height:100%;width:100%"></canvas>
The code is only a pseudo-code, but it shows what i like to do.
Your code is using css for the canvas image, that not always looks the way we think...
I will recommend you to draw everything from scratch, here is a starting point:
canvas = document.getElementById('mainCanvasMap');
ctx = canvas.getContext('2d');
zoomCanvas = document.getElementById('zoomCanvas');
zoomCtx = zoomCanvas.getContext('2d');
var pos = {x:0, y:40}
image = document.getElementById('source');
image.onload = draw;
function draw() {
ctx.drawImage(image,0,0);
setInterval(drawZoom, 80);
}
function drawZoom() {
// simple animation on the x axis
x = Math.sin(pos.x++/10 % (Math.PI*2)) * 20 + 80
zoomCtx.drawImage(image, x, pos.y, 200, 100, 0, 0, 400, 200);
}
<canvas id="mainCanvasMap"></canvas>
<canvas id="zoomCanvas"></canvas>
<img id="source" src="https://image.jimcdn.com/app/cms/image/transf/dimension=624x10000:format=jpg/path/s7d1eecaa5cee1012/image/i4484f962de0bf3c2/version/1543751018/image.jpg" style="display:none">

FabricJS prevent canvas.clipTo from clipping canvas.backgroundImage

I want to set a global clipTo in my Fabric-powered Canvas that will affect all user-added layers. I want a background image and an overlay image, which are unaffected by this clip mask.
Example:
Here's what's happening in this photo:
A canvas overlay image makes the t-shirt look naturally wrinkled. This overlay image is mostly transparent
A background image in the exact shape of the t-shirt was added, which is supposed to make the t-shirt look blue
A canvas.clipTo function was added, which clips the canvas to a rectangular shape
A user-added image (the famous Fabric pug) was added
I want the user-added image (the pug) to be limited to the rectangular area.
I do not want the background image (the blue t-shirt shape) affected by the clip area.
Is there a simple way to accomplish this? I really don't want to have to add a clipTo on every single user layer rather than one tidy global clipTo.
You can play with a JS fiddle showing the problem here.
I came here with the same need and ultimately found a solution for what I'm working on. Maybe it helps:
For SVG paths, within the clipTo function you can modify the ctx directly prior to calling render(ctx) and these changes apply outside the clipped path o. Like so:
var clipPath = new fabric.Path("M 10 10 L 100 10 L 100 100 L 10 100", {
fill: 'rgba(0,0,0,0)',
});
var backgroundColor = "rgba(0,0,0, 0.2)";
var opts = {
controlsAboveOverlay: true,
backgroundColor: 'rgb(255,255,255)',
clipTo: function (ctx) {
if (typeof backgroundColor !== 'undefined') {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, 300, 150);
}
clipPath.render(ctx);
}
}
var canvas = new fabric.Canvas('c', opts);
canvas.add(new fabric.Rect({
width: 50,
height: 50,
left: 30,
top: 30,
fill: 'rgb(255,0,0)'
}));
You can of course add an image instead of a color, or whatever else you want done. The trick I've found is to put it in the clipTo function on the ctx directly.
here's a fiddle
One (sorta hacky) solution: set a CSS background image on your canvas element, as shown in https://jsfiddle.net/qpnvo3cL/
<canvas id="c" width="500" height="500"></canvas>
<style>
background: url('http://fabricjs.com/assets/jail_cell_bars.png') no-repeat;
</style>
<script>
var canvas = window._canvas = new fabric.Canvas('c');
canvas.clipTo = function(ctx) {
ctx.rect(100,100,100,100);
}
</script>
Have you tried clipping a fabric Group? You could make the whole shirt one canvas. The center graphics would be one Group which you clip to where you want it. The white t-shirt and the blue overlay would of course not be part of the clipped group.
Here's an example of clipping a group:
var rect = new fabric.Rect({width:100, height: 100, fill: 'red' });
var circle = new fabric.Circle({ radius: 100, fill: 'green' });
var group1 = new fabric.Group([ circle, rect ], { left: 100, top: 100 });
canvas.add(group1);
group1.clipTo = function(ctx) {
ctx.rect(50,50,200,200);
};
See this jsfiddle I made: https://jsfiddle.net/uvepfag5/4/
I find clip rather slow so I tend to use globalCompositeOperation to do masking.
If you really need to use clip then use it in conjunction with save and restore.
// ctx is canvas context 2d
// pug is the image to be clipped
// draw your background
ctx.save(); // save state
ctx.rect(100,100,100,100); // set the clip area
ctx.clip(); // apply the clip
ctx.drawImage(pug,x,y); // draw the clipped image
ctx.restore(); // remove the clipping
// draw the other layers.
or you can
// draw background
ctx.globalCompositeOperation = "xor"; // set up the mask
ctx.fillRect(100,100,100,100); // draw the mask, could be an image.
// Alpha will effect the amount of masking,
// not available with clip
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(pug,x,y); // draw the image that is masked
ctx.globalCompositeOperation = "source-over";
// draw the stuff that needs to be over everything.
The advantage of composite operations is you have control over the clipping at a per pixel level, including the amount of clipping via the pixel alpha value

Canvas moving shadow after clearRect

I am new to HTML5 and start learning canvas.
Currently, I am using canvas to make some objects rotate. The rectangle I created do move, however, I suffer from a problem, after the object move, some shadows remain as you can see from the image I captured.
I just want to have the rectangle, and not including the blue background clumsy stuff. I try to use different browsers to view this HTML5 document, but same problem comes out. Is this a problem of my computer, or is it a problem of the code? If so, how can I solve it?
I have also attached my source code of rotating rectangle example in jsFiddle: http://jsfiddle.net/hphchan/ogoj9odf/1/
Here is my key code:
In Javascript:
function canvaScript() {
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.translate(200, 200); // fix the origin as center of the canvas
rotating(context);
}
function rotating(context) {
context.clearRect(-50, -100, 100, 200); // why boundaries, shadows exist??
context.rotate(Math.PI/180);
context.fillStyle = '#0000FF';
context.fillRect(-50, -100, 100, 200);
setTimeout(function() {rotating(context)}, 100);
}
In HTML
<body onload="canvaScript()">
<canvas id="myCanvas" width="400" height="400"></canvas>
</body>
Thanks for answering.
This problem probably comes from the anti-aliasing.
You can see it by clearing directly after you drawn your rotated shape :
function canvaScript() {
var context = canvas.getContext('2d');
context.translate(200, 200); // fix the origin as center of the canvas
context.rotate(Math.PI/4);
rotating(context);
}
function rotating(context) {
context.fillStyle = '#0000FF';
context.fillRect(-50, -100, 100, 200);
context.clearRect(-50, -100, 100, 200);
}
canvaScript();
<canvas id="canvas" width="400" height="400"></canvas>
So one solution to workaround this is to clear a slightly larger clearRect than the rect you just drawn.
function canvaScript() {
var context = canvas.getContext('2d');
context.translate(200, 200); // fix the origin as center of the canvas
rotating(context);
}
function rotating(context) {
// clear one extra pixel in all directions
context.clearRect(-51, -101, 102, 202);
context.rotate(Math.PI/180);
context.fillStyle = '#0000FF';
context.fillRect(-50, -100, 100, 200);
setTimeout(function() {rotating(context)}, 100);
}
canvaScript();
<canvas id="canvas" width="400" height="400"></canvas>

In the HTML5 canvas, is there a way to rotate images in different angles?

Currently there are are two images I would like to rotate on the canvas, I tried save and restore but didn't work
function SetCanvas()
{
var canvas = document.getElementById('pic1');
if(canvas.getContext)
{
var ctx = canvas.getContext('2d');
// ctx.save();
ctx.rotate(0.5);
var image = new Image();
image.src ='ME.JPG';
image.onload = function(){
ctx.drawImage(image, 90,0,200,100);
};
}
//ctx.restore();
var canvas2 = document.getElementById("pic2");
var image2 = new Image();
image2.src = 'ME2.JPG';
if(canvas2.getContext)
{
image2.onload = function(){
ctx2=canvas2.getContext('2d');
ctx2.drawImage(image2, 0,0,200,100);
};
}
}
<ul id="picsCanvas" style="overflow:hidden;white-space:nowrap; list-style-type:none;">
<li style=" display:inline; float:left" id="first">
<canvas ID="pic1" width="300" height="360" ></canvas>
</li>
<li id="second" style="margin-top:0px; display:inline; float:left; position:absolute ">
<canvas id="pic2" width="300" height="360" style="position:absolute" ></canvas>
</li>
</ul>
Please note that the code might not be correct as it is something I did a while ago, I just want to get an idea of how to do it and if it is possible... thanks for your help.
The images are loading asynchronously. This means that that entire function (minus the onload handlers for the images happens first. Then when the images are loaded, their handlers are called. This happens in a second pass. By the time this happens, you already rotated and restored the canvas, effectively wiping the rotation out.
The simple fix is to rotate and restore the canvas inside each of the image onload handlers.
These two links give a pretty good explanation and example of how to rotate with HTML5 canvas
https://developer.mozilla.org/en/Drawing_Graphics_with_Canvas
https://developer.mozilla.org/en/Canvas_tutorial/Basic_animations
You set the different angles when you rotate (see code example below).
The general gist of it is:
1) save the context
2) transform to, usually, the center of the image
3) rotate
4) transform back
5) draw image
6) restore
In your case, with two images, you need to transform the origin to the second image before you make the second rotation call. Below is a simplified example rotating one image. Get that sorted and then make the second transform/rotate.
Example:
var canvas = document.getElementById("yourCanvas");
var ctx = canvas.getContext("2d");
var angle = 0;
window.setInterval(function(){
angle = angle+1;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.fillStyle = "#FF0000";
// first image
ctx.translate(150,200);
ctx.rotate( angle*Math.PI/180 ); // rotate 90 degrees
ctx.translate(-150,-200);
ctx.fillStyle = "black";
ctx.fillRect(100, 150, 100, 100);
ctx.fill();
ctx.restore();
}, 5);​

Removing an image from a canvas in HTML5

there's an example, which loads 2 images:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var img1 = new Image();
img.src = "/path/to/image/img1.png";
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
var img2 = new Image();
img2.src = "/path/to/image/img2.png";
img2.onload = function() {
ctx.drawImage(img2, 100, 100);
};
I need to remove(replace) img2 from canvas. What is the best way to do it?
I think maybe you misunderstand what a Canvas is.
A canvas is essentially a 2 dimensional grid of pixels along an 'X' axis and a 'Y' axis. You use the API to draw pixels onto that canvas, so when you draw an image you're basically drawing the pixels that make up that image onto your canvas. The reason there is NO method that lets you just remove an image, is because the Canvas doesn't know there's an image there in the first place, it just see pixels.
This is unlike the HTML DOM (Document Object Model) where everything is a HTML element, or an actual 'thing' you can interact with, hook-up script events to etc. this isn't the case with stuff you draw onto a Canvas. When draw a 'thing' onto a Canvas, that thing doesn't become something you can target or hook into, it's just pixels. To get a 'thing' you need to represent your 'thing' in some way such as a JavaScript object, and maintain a collection of these JS objects somewhere. This how how Canvas games work. This lack of a DOM-like structure for Canvas makes rendering very fast, but can be a pain for implementing UI elements that you can easily hook into and interact with, remove etc. For that you might want to try SVG.
To answer your question, simply paint a rectangle onto your Canvas that covers up your image by using the same X/Y coords and dimensions you used for your original image, or try Pointy's solution. 'Cover-up' is probably the wrong terminology, since you're actually replacing the pixels (there are no layers in Canvas).
It's not clear what you want the canvas to show when the image is gone. If you want it to be transparent, you could get the image data and fill it with transparent pixels:
var img = ctx.createImageData(w, h);
for (var i = img.data.length; --i >= 0; )
img.data[i] = 0;
ctx.putImageData(img, 100, 100);
where "w" and "h" would be the width and height of your original image.
edit — if you just want another image there, why not just put one there? It will overwrite whatever pixels are there on the canvas.
You can use clearRect() function to clear the image area.Rather then clearing whole context you can clear only the image area using this:
ctx.clearRect(xcoordinate_of_img1,ycoordinate_of_img1,xcoordinate_of_img1 + img1.width ,ycoord_of_img1 +img1.height );
If what "Sunday Ironfoot" said is right, then the best way to remove an image is by drawing the images once again from scratch. For this, you need to have an array of images and draw only the ones you use. For example,
function EmptyClass{};
var img=new Array();
img[0]=new EmptyClass;
img[0].i=new Image();
img[0].src="yourfile1.jpg";
img[0].enabled=true;
img[1]=new EmptyClass;
img[1].i=new Image();
img[1].src="yourfile2.jpg";
img[1].enabled=false;// <-------- not enabled, should not be drawn equivalent to removing
img[2]=new EmptyClass;
img[2].i=new Image();
img[2].src="yourfile3.jpg";
img[2].enabled=true;
for(var i=0;i<3;i++){
if(img[i].enabled)ctx.drawImage(img[i], 100, 100);
}
P.S. I am creating an engine for javascript canvas. Will post it within a week
Peace
You can erase an image by drawing the same image again, using a different globalCompositeOperation
ctx.globalCompositeOperation ="xor"
ctx.drawImage(img2, 100, 100);
See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
Unlike drawing things yourself, if you 'replace' THE image on a canvas, the old one is still there.
Canvas c2;
...
if (null != Image2) {
var ctx = c2.getContext("2d");
ctx.clearRect(0, 0, c2.width, c2.height);
}
Can you overlay canvas objects (I guess I should try before asking, you can -1 one me for being lazy). I guess I'd be interested in have one canvas element as a background, and then another for a layer objects that pop in and out of view. Might be a little more efficient then having to redraw every image if one gets deleted or moved. I'll play around and see what I can find.
There is ``ctx.clearRect(x, y, w, h)'' but this is not a good way to remove the shape, because it will remove any full or partial shapes in the same area of the removed shape. This shouldn't happen, and may remove one or more shapes, I've found it's best to save all your shapes in a list that usually comes from the database using backend language or ajax request, and add for it's shape object an identifier, when you need to remove a shape just remove that shape from the list using the id or the index, then Redraw the canvas with this new array of shapes without a deleted shape, the next time the page loads, this shape will not be added to this list, because it should be deleted from database.
const projectStamps = [{image_id: 'scream', x: 100, y: 100, id: 1}, {image_id: 'scream', x: 100, y: 100, id: 2}, {image_id: 'scream', x: 50, y: 0, id: 3}, {image_id: 'scream', x: 150, y: 0, id: 4}];
let currentShapes = [];
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
function validStampObj(stamp){
if (typeof(stamp.x) !== 'number' || typeof(stamp.y) !== 'number' || typeof(stamp.image_id) === 'undefined' || !document.getElementById(stamp.image_id)){
return false;
} else {
return true;
}
}
function addStamp(stamp){
if (!validStampObj(stamp)){
console.log("can not add stamp, invalid object");
return false;
}
const image = document.getElementById(stamp.image_id);
stamp['w'] = image.getBoundingClientRect().width;
stamp['h'] = image.getBoundingClientRect().height;
ctx.drawImage(image, stamp.x, stamp.y, stamp.w, stamp.h);
currentShapes.push(stamp);
return stamp;
}
let id = 1;
window.onload = function() {
drawProject();
};
function clearCanvas(){
currentShapes = [];
ctx.clearRect(0, 0, canvas.width, canvas.height);
return true;
}
const projectImage = document.getElementById("project_image");
function drawProject(){
if (!projectImage){console.log('missing project image element');return false;}
clearCanvas();
ctx.drawImage(projectImage,0,0);
projectStamps.forEach( (stamp)=>{
addStamp(stamp);
});
}
function removeStamp(targetId){
let targetI = false;
for (let i=0; i<projectStamps.length; i++){
if (projectStamps[i].id == targetId){
targetI = i;
break;
}
}
if (targetI !== false){
/* remove the stamp from drawing stamps list and redraw the data */
projectStamps.splice(targetI,1);
drawProject();
}
}
setTimeout( ()=>{
removeStamp(3);
console.log("removed icon with id 3");
}, 2500 );
<p>Image to use:</p>
<img id="scream" width="35" height="35"
src="https://i.ibb.co/wYyc259/iconimage.png" alt="The Scream">
<img id="project_image" width="450" height="300"
src="https://i.ibb.co/sK5HtQy/bulding-image.png" style="position:absolute;left:-15455px;">
<p>Canvas:</p>
<button onclick="drawProject()">Redraw things</button>
<canvas id="myCanvas" width="450" height="300"
style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
notes if you used clearRect in this example it will remove the part of main image of the canvas not just the icon with id 3 like this code does hope it helps.

Categories