I have an HTML5 Canvas. I am using the KineticJS(KonvaJS) canvas library. On a blank canvas I dram an image as shown in the figure below. Now I want to create a circle Shape which can be used to erase parts of the image. The red circle in the image is the eraser.
How can I erase parts of an Image on HTML5 Canvas?
You can use Compositing to "erase" pixels.
Specifically you use destination-out compositing.
KineticJS does not support compositing, but you still have a couple of options:
(Note: KineticJS has become KonvaJS and I haven't checked whether KonvaJs supports compositing. If it now does, just use destination-out compositing inside KonvaJS)
Option#1: Use a native canvas element as your Kinetic.Image source
Create an in-memory html5 canvas using var c=document.createElement,
Resize the canvas to image size,
drawImage your image onto the canvas,
Create a Kinetic.Image and set its image property to a reference to the native canvas. The Kinetic.Image will display whatever is drawn onto the native canvas.
var kImage=new Kinetic.Image({
...
image:c,
...
Set the canvas Compositing to cause new drawings to "erase" existing pixels:
c.globalCompositeOperation='destination-out';
Listen for drag events on your circle-eraser. Use those events to draw a circle on the canvas that move just like the Kinetic circle-eraser moves. Since the canvas's compositing is set to "erase", new drawings of the circle on the canvas will erase the image on the canvas.
Your Kinetic.Image exactly reflects its canvas source (var c), so your Kinetic.Image will also display the image being erased in response to the Kinetic circle-eraser movements.
Option#2: Use a Kinetic.Shape
You can do the same operation as Option#1 by creating a Kinetic.Shape on a separate layer and getting a reference to the native canvas context using:
var ctx = myShapeLayer.getContext()._context;
This is a weaker option because KineticJS will redraw the shape--causing your erasing to be undone. Therefore you must do the additional step of saving all your circle-eraser's movements and replaying those movements (in drawFunc) to redo your erasing.
Thanks for markE for his detailed answer,
I have tried to get the context from the Konva.Layer() and it worked.
var freeHandDrawingImage = new Image();
freeHandDrawingImage.onload = function() {
var context = freeHandDrawingLayer.getContext('2d');
context.drawImage(this, 0,0);
context.globalCompositeOperation='destination-out';
freeHandDrawingLayer.draw();
};
freeHandDrawingImage.src = "image.png";
and I have used the Konva.Shape to erase by "destination-out" and draw free draw by custom "source-over":
freeDrawingType = 'brush';
isFreeDrawingMode = false;
isPaint = false;
lastPointerPosition = {};
drawFreeDrawings = function(){
var freeDraw = new Konva.Shape({
name: "freeDraw",
stroke: 'black',
strokeWidth: 5,
closed : false,
sceneFunc: function(context){
// free draw quad
debugger;
if(isPaint){
if (freeDrawingType === 'brush') {
context.globalCompositeOperation = 'source-over';
}
if (freeDrawingType === 'eraser') {
context.globalCompositeOperation = 'destination-out';
}
context.beginPath();
context.moveTo(lastPointerPosition.x, lastPointerPosition.y);
var newPosition = stage.getPointerPosition();
context.lineTo(newPosition.x, newPosition.y);
context.stroke();
debugger;
lastPointerPosition = newPosition;
context.strokeShape(this);
}
}
});
freeHandDrawingLayer.add(freeDraw);
// now we need to bind some events
// we need to start drawing on mousedown
// and stop drawing on mouseup
selectionBoxBackground.on('mousedown', function() {
if(isFreeDrawingMode){
isPaint = true;
lastPointerPosition = stage.getPointerPosition();
stage.draw();
}
});
selectionBoxBackground.on('mouseup', function() {
if(isFreeDrawingMode){
isPaint = false;
}
});
// and core function - drawing
selectionBoxBackground.on('mousemove', function() {
if (!isPaint) {
return;
}
freeHandDrawingLayer.draw();
});
}
In plain JavaScript this is pretty straight forward.
First get your canvas and drawing context ready:
var context=document.getElementById("your_canvas_id").getContext("2d");
var image=document.getElementById("your_image_id");
Now you want to draw the image to the context:
context.drawImage(image,0,0,image.width,image.height,0,0,image.width,image.height);
Now, when you want to erase part of your image, just draw over the canvas:
var x=y=radius=10;// Circle coordinates and radius.
context.fillStyle="#ffffff";// Your eraser color (not transparent)
context.beginPath();
context.arc(x,y,radius,0,Math.PI*2);
context.fill();
This only simulates erasing, however. If you want what you erase to be transparent afterwards, you might look into context.clearRect, but I'm not sure how you would do that with a circle.
Related
Here is my canvas on jsfiddle:
https://jsfiddle.net/vzrandom/fkho6grf/8/
I'm using simplex-noise.js and dat.GUI to create movement of particles. There is simulated click on canvas every 5 seconds. On click first animation is coming in background and new animation starts.
My problem is that animation on click starts too abruptly. I would like to have some kind of fade in of particles.
It seems like a simple problem but somehow I can't get it how to make fade in of elements that are inside the canvas - not entire canvas himself.
Entire code is on jsfiddle, here is part that handles the click:
function onCanvasClick(e) {
context.save();
context.globalAlpha = 1;
context.fillStyle = Configs.backgroundColor;
context.fillRect(0, 0, screenWidth, screenHeight);
context.restore();
simplexNoise = new SimplexNoise();
}
You need to render to an offscreen canvas.
Just create a second canvas
var canvas2 = document.createElement("canvas");
canvas2.width = canvas.width;
canvas2.height = canvas.height;
var ctx2 = canvas.getContext("2d");
Then for all your drawing calls use the background canvas 2dContext
To do the fade just render that canvas onto the display canvas setting alpha to make it fade.
The function called by requestAnimationFrame is passed a hi resolution time as the first argument. The code below is for the update function. Note that if you are using a polyfill for requestAnimationFrame you should use one that matches the standard.
var fadeTime = 1; // one second
var fadeTimeStart = undefined; // when undefined then this indicates start of fade
function update(time){
// render your particles to offscreen canvas
if(fadeTimeStart === undefined){ // get the current time as start
fadeTimeStart = time;
}
// get amount of fade
var fTime = (time - fadeTimeStart) / fadeTime;
// is it fading
if(fTime < 1){ // yes
ctx.globalAlpha = fTime;
clearRect(0,0,canvas.width,canvas.height); // clear last rendered scene
ctx.drawImage(canvas2,0,0); // draw the offscreen canvas
}else{
// you may or may not have to clear the canvas
ctx.drawImage(canvas2,0,0); // if no fade and assuming canvas is opaque then just draw the canvas onto the display.
}
requestAnimationFrame(update);
}
Then in the click event to start a new fade in just set the fadeTimeStart = undefined and it will start a new fade in.
I don't know if this is the effect you want to achieve, but you might get away with clearing the canvas a little on each iteration by filling it with a semi-transparent color in update function, instead of clearing it completely on each click.
See my fork here: https://jsfiddle.net/640e32ua/
Main change is changing Configs.backgroundColor to something semi-transparent and adding these two lines to update:
context.fillStyle = Configs.backgroundColor;
context.fillRect(0, 0, screenWidth, screenHeight);
I have drawn an image on the canvas.
I want the user to click on the canvas to crop a portion of the image.
How can I do this?
Here's an outline to get you started:
Draw the image onto the canvas
var canvas=document.getElementById('myCanvas');
canvas.drawImage(yourImageObject,0,0);
Listen for mousedown events.
canvas.onmousedown=function(e){handleMouseDown(e);};
Have the user click in the top-left [x0,y0] and bottom-right [x1,y1] corners where they want to crop and record those 2 mouse positions.
The cropping rectangle is defined like this:
var x=x0;
var y=y0;
var width=x1-x0;
var height=y1-y0;
Create a second canvas element and size it to the cropping size:
var secondCanvas = document.createElement('canvas');
secondCanvas.width = width;
secondCanvas.height = height;
document.body.appendChile(secondCanvas);
Use the clipping version of drawImage to draw the cropping rectangle from the first canvas onto the second canvas
secondCanvas.drawImage(canvas,
x,y,width,height, // clip just the cropping rectangle from the first canvas
0,0,width,height // draw just the cropped part onto the first canvas
);
The user's selected portion of the image is now on the second canvas.
If you want to convert the second canvas into an image object, you can do this:
var img=new Image();
img.onload=start;
img.src=secondCanvas.toDataURL();
function start(){
// at this point, img contains the cropped portion of the original image
}
I am creating a drawing app, and using this function to download the canvas image.
function download() {
var dt = canvas.toDataURL('image/png');
this.href = dt;
};
I want to set the canvas background to white before downloading because on mobile, the images are very distorted and black. Any ideas?
You may want to draw a white rectangle the size of the entire canvas, beneath the actual content of the canvas.
// get the canvas 2d context
var ctx = canvas.getContext('2d');
// set the ctx to draw beneath your current content
ctx.globalCompositeOperation = 'destination-over';
// set the fill color to white
ctx.fillStyle = 'white';
// apply fill starting from point (0,0) to point (canvas.width,canvas.height)
// these two points are the top left and the bottom right of the canvas
ctx.fillRect(0, 0, canvas.width, canvas.height);
You have to apply these lines before generating your toDataUrl() stream.
Idea taken from: http://www.mikechambers.com/blog/2011/01/31/setting-the-background-color-when-generating-images-from-canvas-todataurl/
I want to draw an image to canvas and then allow user to draw some sketch on it by sketch.js. Now the situation is:
1.the image has been drawn on the canvas successfully
2. but when my mouse over the canvas, then image disappeared, and the canvas shows empty
3. when I dragged the mouse, some sketch shows on the empty canvas(the sketch looks correct)
so, I've made both functions right, but I'm confused about the part in between. I hope to draw the sketch on the canvas with the image on it. Here is my code:
index.html:
<canvas id="mysketch" width="578" height="400" style="margin: 0px; "></canvas>
canvas.js:
var mysketch = jQuery("#mysketch");
// draw the captured image to canvas
var displayImage = function() {
var context = mysketch.get(0).getContext("2d");
var image = new Image();
image.onload = function() {
context.drawImage(image, 0, 0);
}
// prepend the required image info again
image.src = dataURL;
// call sketch.js to draw sketch on the canvas
mysketch.sketch({defaultColor: "#ff0"});
}
I think that, on your call to sketch method
sketch.js is clearing the canvas first then allows you to draw something on it.
As you can see in the link here, in the 3rd example (i.e. Tools Example) the image is not drawn by drawImage method.
It is actually the background of the canvas which will always be there as background, whatever you draw on it.
So what you can do is:
either do same thing as in the example i.e. set your image as background,
or make a new canvas just behind your canvas with same width, height and on same location and draw your image on it. and do your sketch thing on the second one.
How to resize image already drawed in canvas?
I tried this with no luck (image is showing, but it doesn't resize):
var drawBall = function(mouseX, mouseY){
ballimg = new Image();
ballimg.onload = function(){
ctx.drawImage(ballimg, mouseX-25, mouseY-25);
ballimg.height = 5;
throwed = true;
};
ballimg.src = "ball2.png";
};
You can not resize an object which is drawn on the canvas.
What you need to do is redraw your object.
clear the context where the old Ball is located ctx.clearRect(x,y,x2,y2)
and draw a new Ball with the new size.
If you want to animate things on the canvas. The way you do that is to keep track of all your objects and redraw the canvas(every single object) for every frame.