how to erase the shape not context in html5 canvas - javascript

I need to erase any of the shapes(like circle, rect) but not whole context, using another brush tool.
var brush = new Path.Circle({
center: event.point,
radius: 35,
fillColor: 'red',
});
var eraser = new Path.Circle({
center: event.point,
radius: 35
});
I tried "destination-out", "clearRect", but nothing work. I need the result exactly similar the attached image.

You can use a second canvas, where you'll draw your background, and then, thanks to the destination-atop composite mode, keep only the wanted circle of this image, that you'll draw back to your main canvas.
var eraser = function(evt){
var x = evt.clientX - this.offsetLeft;
var y = evt.clientY - this.offsetTop;
oCtx.globalCompositeOperation = 'source-over';
oCtx.drawImage(img, 0,0);
oCtx.globalCompositeOperation = 'destination-atop';
oCtx.beginPath();
oCtx.arc(x, y, 25, 0, Math.PI*2);
oCtx.fill();
ctx.drawImage(offCan, 0,0);
}
main.addEventListener('mousemove', eraser);
var ctx = main.getContext('2d');
var offCan = main.cloneNode(true);
var oCtx = offCan.getContext('2d');
var draw = function(){
ctx.drawImage(this, 0,0);
ctx.fillStyle = 'red';
for(var i=0; i<12; i++){
ctx.beginPath();
ctx.arc(Math.random()*300, Math.random()*150, (Math.random()*25)+10, 0, Math.PI*2);
ctx.fill();
}
};
var img = new Image();
img.onload = draw;
img.src = "http://lorempixel.com/300/150";
// you should not include it in the doc, it's just for explaining how it works.
document.body.appendChild(offCan)
<canvas id="main"></canvas>

Related

Rotate an image on top of another canvas image

Currently, I am making a game and in need of making the image rotate toward the cursor. I am using node but the image is in a js tag in the HTML file that uses ctx to draw the image.
If I put a ctx.rotate(angle); pretty much anywhere it will rotate everything; player, map, etc. I need help so that only the player is rotated
this is a simplified version of my code:
<canvas id="ctx" width="200" height="200"></canvas>
<script>
//game
var ctx = document.getElementById("ctx").getContext("2d");
var WIDTH = 200;
var HEIGHT = 200;
var Img = {};
//player
Img.player = new Image();
Img.player.src = '/client/img/player.png';
var Player = function(/*node*/){
ctx.drawImage(Img.player, ...);
}
//map
Img.map = new Image();
Img.map.src = '/client/img/map.png';
//display everything
setInterval(function(){
ctx.clearRect(0,0,200,200);
drawMap();
for(var i in Player.list)
Player.list[i].draw();
},1000/60);
//functions
//move map so that player is always in the middle
var drawMap= function(){
var x = WIDTH/2 - Player.list[/*node*/].x;
var y = HEIGHT/2 - Player.list[/*node*/].y;
ctx.drawImage(Img.map,x,y);
}
</script>
Here's an example of what you may be looking for
const ctx = document.getElementById("ctx").getContext("2d");
const WIDTH = 500,
HEIGHT = 500;
document.getElementById("ctx").height = HEIGHT;
document.getElementById("ctx").width = WIDTH;
var Player = {
x: 50,
y: 55,
angle: 0
}
document.addEventListener("mousemove", (event) => {
var x = event.clientX - Player.x,
y = event.clientY- Player.y,
angle = Math.atan2(y,x);
Player.angle = angle
})
function draw() {
window.requestAnimationFrame(draw);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
ctx.save();
ctx.translate(Player.x, Player.y);
ctx.rotate(Player.angle);
ctx.translate(-Player.x, -Player.y);
ctx.fillRect(Player.x, Player.y, 20, 20);
ctx.restore();
ctx.fillRect(150, 50, 20, 20);
}
draw();
<canvas id="ctx"></canvas>
jsfiddle here
Hope this helps!

Multi-layer markers for google.maps.api using js (angularJS)

I am working on an app which uses a map and I chose google maps for this. This map must contain different types of markers. Each marker has 2 layers, 1 - marker background, 2 - an image which I get from my server.
Best solution I found so far is using canvas to draw an image and then return canvas.toDataUrl for marker icon url.
Here is some code:
getting url:
function createMarker(width, height, radius) {
var canvas, context;
canvas = document.getElementById("canvas");
canvas.width = width;
canvas.height = height;
context = canvas.getContext("2d");
context.clearRect(0,0,width,height);
context.fillStyle = "rgba(255,255,0,1)";
context.strokeStyle = "rgba(0,0,0,1)";
context.beginPath();
context.moveTo(radius, 0);
context.lineTo(width - radius, 0);
context.quadraticCurveTo(width, 0, width, radius);
context.lineTo(width, height - radius);
context.quadraticCurveTo(width, height, width - radius, height);
context.lineTo(radius, height);
context.quadraticCurveTo(0, height, 0, height - radius);
context.lineTo(0, radius);
context.quadraticCurveTo(0, 0, radius, 0);
context.closePath();
var img1 = new Image(); img1.src = 'img/pin.png'; img1.onload = function () {
context.drawImage(img1, 0, 0, 25, 25);
};
var img2 = new Image(); img2.src = 'img/cluster.png';
img2.onload = function () {
context.drawImage(img2, 0, 0, 25, 25);
};
context.fill();
context.stroke();
return canvas.toDataURL();
}
And in the browser I get this:
Under my map I add canvas witch which url I use for marker
It looks like the marker ignores canvas images.
What am I doing wrong? Or do you have any better solution for my problem?
My solution for this problem is to create marker inside onload function of image.
Example:
var canvas, context;
canvas = document.getElementById("canvas");
canvas.width = width;
canvas.height = height;
context = canvas.getContext("2d");
var img1 = new Image();
img1.src = 'img/pin.png';
img1.onload = function () {
context.drawImage(img1, 0, 0, 25, 25);
var marker = new google.maps.Marker({
map: map,
position: {lat: $scope.data[el].latitude, lng: $scope.data[el].longitude},
icon: {url: canvas.toDataURL(), size: new google.maps.Size(25, 25)}
})
};

rotate one of the 2 canvas objects placed in canvas

I'm developing web app using canvas and I made three. canvas, canvas_panorama and canvas_image.
First one is something like main canvas, conteiner for the others. canvas_panorama is a background for canvas_image.
After canvas is right clicked, I'm computing angle to rotate canvas_image:
function getAngle( e, pw /*canvas*/ ){
var offset = pw.offset();
var center_x = (offset.left) + ($(pw).width() / 2);
var center_y = (offset.top) + ($(pw).height() / 2);
var mouse_x = e.pageX;
var mouse_y = e.pageY;
var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
angle = radians;
}
After I have an angle I'm trying to rotate canvas_image like this:
function redraw(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
ctx.clearRect( p1.x, p1.y, p2.x-p1.x, p2.y-p1.y );
canvas_image_ctx.drawImage(image_img, 0, 0, 150, 150);
canvas_panorama_ctx.drawImage(panorama_img, 0, 0, 600, 300);
canvas_panorama_ctx.drawImage(canvas_image, 20, 20);
// rotate panorama_img around its center
// x = x + 0.5 * width
// y = y + 0.5 * height
canvas_panorama_ctx.translate(95, 95);
canvas_panorama_ctx.rotate(angle);
// translate to back
canvas_panorama_ctx.translate(-95, -95);
ctx.drawImage(canvas_panorama, 0, 0);
}
But this rotates both canvas_image and canvas_panorama. It should only rotate canvas_image
JSFiddle to show you my problem
I think you are confusing yourself with this idea of multiple canvases.
Once in the drawImage() method, every of your canvases are just images, and could be just one or even just plain shapes.
Transformation methods do apply to the canvas' context's matrix, and will have effect only if you do some drawing operations when they are set.
Note : To reset your context matrix, you can either use save(); and restore() methods which will also save all other properties of your context, so if you only need to reset the transform, then it's preferred to simply reset the transformation matrix to its default : ctx.setTransform(1,0,0,1,0,0).
Here is a simplified example to make things clearer :
var ctx = canvas.getContext('2d');
// a single shape, with the border of the context matrix
var drawRect = function(){
ctx.beginPath();
ctx.rect(10, 10, 50, 20);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.stroke();
};
// set the color of our shapes
var gradient = ctx.createLinearGradient(0,0,70,0);
gradient.addColorStop(0,"green");
gradient.addColorStop(1,"yellow");
ctx.fillStyle = gradient;
// here comes the actual drawings
//we don't have modified the transform matrix yet
ctx.strokeStyle = "green";
drawRect();
// here we translate of 100px then we do rotate the context of 45deg
ctx.translate(100, 0)
ctx.rotate(Math.PI/4)
ctx.strokeStyle = "red";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
// here we move of 150px to the right and 25px to the bottom
ctx.translate(150, 25)
ctx.strokeStyle = "blue";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
<canvas id="canvas" width="500" height="200"></canvas>
In your code, you are setting the transformations on the canvas that does represent your image, and you do draw every of your canvases at each call.
What you want instead, is to set the transformation on the main canvas only, and draw the non-transformed image :
var main_ctx = canvas.getContext('2d');
var img_canvas = canvas.cloneNode();
var bg_canvas = canvas.cloneNode();
var angle = 0;
// draw on the main canvas, and only on the main canvas
var drawToMain = function(){
// first clear the canvas
main_ctx.clearRect(0,0,canvas.width, canvas.height);
// draw the background image
main_ctx.drawImage(bg_canvas, 0,0);
// do the transforms
main_ctx.translate(img_canvas.width/2, img_canvas.height/2);
main_ctx.rotate(angle);
main_ctx.translate(-img_canvas.width/2, -img_canvas.height/2);
// draw the img with the transforms applied
main_ctx.drawImage(img_canvas, 0,0);
// reset the transforms
main_ctx.setTransform(1,0,0,1,0,0);
};
// I changed the event to a simple onclick
canvas.onclick = function(e){
e.preventDefault();
angle+=Math.PI/8;
drawToMain();
}
// a dirty image loader
var init = function(){
var img = (this.src.indexOf('lena')>0);
var this_canvas = img ? img_canvas : bg_canvas;
this_canvas.width = this.width;
this_canvas.height = this.height;
this_canvas.getContext('2d').drawImage(this, 0,0);
if(!--toLoad){
drawToMain();
}
};
var toLoad = 2;
var img = new Image();
img.onload = init;
img.src = "http://pgmagick.readthedocs.org/en/latest/_images/lena_scale.jpg";
var bg = new Image();
bg.onload = init;
bg.src = 'http://www.fnordware.com/superpng/pnggradHDrgba.png';
<canvas id="canvas" width="500" height="300"></canvas>

How to draw circles OVER an image background of a HTML5 canvas?

So I'm drawing a world map to a canvas. I want this world map to plot the coordinates of things dynamically by drawing circles with the arc method. I draw the world map to the canvas, get a URL for the canvas, and there are no errors in drawing the circles... The only problem is the circles are being drawn underneath the image background. Here is my code.
// The containing function is a method of an object.
createCanvas: function(done) {
var imageObj = new Image();
imageObj.onload = _.bind(function(){
var canvas = document.createElement("canvas"),
context = canvas.getContext('2d'),
w = imageObj.width,
h = imageObj.height;
canvas.width = w;
canvas.height = h;
context.drawImage(imageObj, 0, 0, w, h);
for(var i = 0; i < arrayOfCoords.length; i++){
var mapPoint = arrayOfCoords[i];
//Invoke a function that gets an object {long: x, lat: y}
var coordinates = returnCoordinatesObject(mapPoint.long, mapPoint.lat)
context.beginPath();
context.arc(coordinates, long, coordinates.lat, 10, 0, Math.PI*2, true);
context.closePath();
context.fill();
}
var canvasImage = canvas.toDataURL('image/png');
console.log(canvasImage);
done.call(this, canvasImage);
}, this);
imageObj.src="img/le-world-map.png";
},
The code isn't erroring so I presume the circles are being drawn, but drawn behind the map. There are other threads on stackoverflow that have asked similar questions but I found them unclear and was unable to implement the solutions suggested...
Thx
--Gaweyne

get area of outline of 2D canvas

I am very nerd in this topic. But still I got some solution from this answer. I need covered area by non-transparent part of image on canvas. Its possible to draw outline for sprite image using globalCompositeOperation .
Now Is it possible to get that area covered by outline for that non-transparent part of image? Is it possible to get covered area in x,y position that I can save it for further use ? OR Is there any way to restrict touch event on source-out area from globalCompositeOperation ?
Currently using code from this answer to draw outline :
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);
}
EDIT:
Using #Kaiido solution.Its taking pixels from non-transparent + outline. I need only non transparent area.
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.crossOrigin = 'anonymous';
img.src = "drawing/templates/drawing-pic4.png";
var outline, origOutline,
outlineCtx;
function draw(color) {
ctx.clearRect(0,0,canvas.width,canvas.height);
// onload
if(typeof color !== 'string') color = 'white';
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 5, // 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 = color;
ctx.fillRect(0,0,canvas.width, canvas.height);
// keep only the outline
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
origOutline = ctx.getImageData(0,0,canvas.width, canvas.height).data;
// store the imageData in a new Canvas
outline = canvas.cloneNode(true);
outlineCtx = outline.getContext('2d')
outlineCtx.drawImage(canvas,0,0);
// draw image in original mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
var w= 10;
canvas.onclick = function(e){
var rect = canvas.getBoundingClientRect();
var x = e.clientX-rect.left,
y = e.clientY-rect.top;
var pixels = ((y*canvas.width)+x)*4;
showLog("pixels: "+pixels);
if(origOutline[pixels+3]!==0)
{
showLog("in out line: "+origOutline[pixels+3]);
}
// not transparent ?
if(outlineCtx.getImageData(x,y,1,1).data[3]!==0){
ctx.strokeStyle = "#0000ff";
ctx.lineWidth = w;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x,y);
ctx.stroke();
ctx.closePath();
}
else{
showLog("else");
}
}
Here is one way :
Use Ken's method to draw the shape, but for the last drawing, instead of setting the globalCompositeOperation property to 'source-out', set it to 'destination-out'. This way you will have only the outline :
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/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 dest-out mode to keep only the outline
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
Now, you can store this outline in a new canvas, and every time you click on your canvas, compare the click event's position to the pixel at same position in your stored canvas :
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.crossOrigin = 'anonymous';
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png";
var outline,
outlineCtx;
function draw(color) {
ctx.clearRect(0,0,canvas.width,canvas.height);
// onload
if(typeof color !== 'string') color = 'red';
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 5, // 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 = color;
ctx.fillRect(0,0,canvas.width, canvas.height);
// keep only the outline
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
// store the imageData in a new Canvas
outline = canvas.cloneNode(true);
outlineCtx = outline.getContext('2d')
outlineCtx.drawImage(canvas,0,0);
// draw image in original mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
canvas.onclick = function(e){
var rect = canvas.getBoundingClientRect();
var x = e.clientX-rect.left,
y = e.clientY-rect.top;
// not transparent ?
if(outlineCtx.getImageData(x,y,1,1).data[3]===255){
draw('green');
}
else
draw('red');
}
<canvas id=canvas width=500 height=500></canvas>
If your outline is unlikely to change very often, it may be interesting to store the imageData instead of calling getImageData every click.
// in the draw function
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
outline = ctx.getImageData(0,0,canvas.width, canvas.height).data;
// in the click event
var pixels = ((y*canvas.width)+x)*4;
if(outline[pixels+3]===255)
// in the outline
else
// out

Categories