Eraser in Canvas HTML 5 - javascript

I create a paint application using HTML5 canvas. I do use a background image. But with eraser. When I delete it, the next image is also deleted. Who can help me?
This HTML :
<form>
<div>
<label for="pencil">Pencil</label>
<input id="pencil" type="radio" name="tool" value="pencil" checked>
</div>
<div>
<label for="eraser">Eraser</label>
<input id="eraser" type="radio" name="tool" value="eraser">
</div>
</form>
<div id="sketch">
<canvas id="paint"></canvas>
</div>
And Javascript:
(function() {
var canvas = document.querySelector('#paint');
var ctx = canvas.getContext('2d');
var sketch = document.querySelector('#sketch');
var sketch_style = getComputedStyle(sketch);
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
// draw image
var img = new Image();
img.src = 'http://cssdeck.com/uploads/media/items/3/3yiC6Yq.jpg';
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// Determine Tool
var tool = 'pencil';
document.querySelector('#pencil').onchange = function() {
if (this.checked)
tool = 'pencil';
// Show Tmp Canvas
tmp_canvas.style.display = 'block';
};
document.querySelector('#eraser').onchange = function() {
if (this.checked)
tool = 'eraser';
// Hide Tmp Canvas
tmp_canvas.style.display = 'none';
};
// Creating a tmp canvas
var tmp_canvas = document.createElement('canvas');
var tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = canvas.width;
tmp_canvas.height = canvas.height;
sketch.appendChild(tmp_canvas);
var mouse = {x: 0, y: 0};
var last_mouse = {x: 0, y: 0};
// Pencil Points
var ppts = [];
/* Mouse Capturing Work */
tmp_canvas.addEventListener('mousemove', function(e) {
mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;
}, false);
canvas.addEventListener('mousemove', function(e) {
mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;
}, false);
/* Drawing on Paint App */
tmp_ctx.lineWidth = 5;
tmp_ctx.lineJoin = 'round';
tmp_ctx.lineCap = 'round';
tmp_ctx.strokeStyle = 'blue';
tmp_ctx.fillStyle = 'blue';
tmp_canvas.addEventListener('mousedown', function(e) {
tmp_canvas.addEventListener('mousemove', onPaint, false);
mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;
ppts.push({x: mouse.x, y: mouse.y});
onPaint();
}, false);
tmp_canvas.addEventListener('mouseup', function() {
tmp_canvas.removeEventListener('mousemove', onPaint, false);
ctx.globalCompositeOperation = 'source-over';
// Writing down to real canvas now
ctx.drawImage(tmp_canvas, 0, 0);
// Clearing tmp canvas
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
// Emptying up Pencil Points
ppts = [];
}, false);
var onPaint = function() {
// Saving all the points in an array
ppts.push({x: mouse.x, y: mouse.y});
if (ppts.length < 3) {
var b = ppts[0];
tmp_ctx.beginPath();
//ctx.moveTo(b.x, b.y);
//ctx.lineTo(b.x+50, b.y+50);
tmp_ctx.arc(b.x, b.y, tmp_ctx.lineWidth / 2, 0, Math.PI * 2, !0);
tmp_ctx.fill();
tmp_ctx.closePath();
return;
}
// Tmp canvas is always cleared up before drawing.
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
tmp_ctx.beginPath();
tmp_ctx.moveTo(ppts[0].x, ppts[0].y);
for (var i = 1; i < ppts.length - 2; i++) {
var c = (ppts[i].x + ppts[i + 1].x) / 2;
var d = (ppts[i].y + ppts[i + 1].y) / 2;
tmp_ctx.quadraticCurveTo(ppts[i].x, ppts[i].y, c, d);
}
// For the last 2 points
tmp_ctx.quadraticCurveTo(
ppts[i].x,
ppts[i].y,
ppts[i + 1].x,
ppts[i + 1].y
);
tmp_ctx.stroke();
};
canvas.addEventListener('mousedown', function(e) {
canvas.addEventListener('mousemove', onErase, false);
mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;
ppts.push({x: mouse.x, y: mouse.y});
onErase();
}, false);
canvas.addEventListener('mouseup', function() {
canvas.removeEventListener('mousemove', onErase, false);
// Emptying up Pencil Points
ppts = [];
}, false);
var onErase = function() {
// Saving all the points in an array
ppts.push({x: mouse.x, y: mouse.y});
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.lineWidth = 5;
if (ppts.length < 3) {
var b = ppts[0];
ctx.beginPath();
//ctx.moveTo(b.x, b.y);
//ctx.lineTo(b.x+50, b.y+50);
ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0);
ctx.fill();
ctx.closePath();
return;
}
// Tmp canvas is always cleared up before drawing.
// ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(ppts[0].x, ppts[0].y);
for (var i = 1; i < ppts.length - 2; i++) {
var c = (ppts[i].x + ppts[i + 1].x) / 2;
var d = (ppts[i].y + ppts[i + 1].y) / 2;
ctx.quadraticCurveTo(ppts[i].x, ppts[i].y, c, d);
}
// For the last 2 points
ctx.quadraticCurveTo(
ppts[i].x,
ppts[i].y,
ppts[i + 1].x,
ppts[i + 1].y
);
ctx.stroke();
};
}());
Link DEMO :
DEMO

If you don't want the background image erased, set your image as a background of your container div (#sketch) instead of drawing it on the canvas itself.
// create a url string of your background image
bkImageURL="url(http://cssdeck.com/uploads/media/items/3/3yiC6Yq.jpg)";
// set the background-image of your container div to that url
sketch.style.backgroundImage = bkImageURL;

Create two canvas elements with different IDs. For this example, let's call the first canvas (or bottom layer) 'background' and the second canvas (or top layer) 'sketchpad':
Use CSS to position one directly over the other. Remember to specify z-values and make 'sketchpad' element a higher value. You can search StackOverflow if you need help completing this step.
Draw your background image onto the 'background' canvas. Here is some sample code:
// specify background canvas element
bgCanvas = document.getElementbyId('background');
// specify canvas context
ctx = bgCanvas.getContext('2d');
// create new background image
var bgImg = new Image();
// draw background image on background canvas
bgImg.onload = function ()
{
//draw background image
ctx.drawImage(bgImg, 0, 0);
}
bgImg.src = './path/to/image/file';
Use the 'sketchpad' canvas for your draw / erase functions.

Related

Stop OutSide Drag And Drop On HTML5 Canvas And Restrict Overlap Of Arc On Drag On Canvas

I have the following problems:
I want to stop drag and drop outside of the canvas.
Stop overlapping of circles on drag(stop drag a circle over another circle).
This is my code which can be found for testing purposes in this link
// get canvas related references
var mouseIsDown = false;
var lastX = 0;
var lastY = 0;
var circles = [];
var offsetX = 0;
var offsetY = 0;
var canvas = null;
var ctx = null;
var canvasWidth = 0;
var canvasHeight = 0;
var count = 0;
makeCircle(20, 20, "salmon");
function makeCircle(x, y, fill){
var i = 0;
for(i =0 ; i< 5 ;i++){
var circle = {
x: x,
y: y,
r: 20,
isDragging: false,
fill: fill
}
circles.push(circle);
y = y + 20;
}
}
var i =0;
for(i = 0; i < circles.length;i++){
updateCanvas();
}
addEventsToCanvas()
// an array of objects that define different rectangles
function drawImageScaled(img) {
var canvas = ctx.canvas;
offsetX = canvas.getBoundingClientRect().left;
offsetY = canvas.getBoundingClientRect().top;
canvasWidth = canvas.width;
canvasHeight = canvas.height;
var hRatio = canvas.width / img.width;
var vRatio = canvas.height / img.height;
var ratio = Math.min(hRatio, vRatio);
var centerShift_x = (canvas.width - img.width * ratio) / 2;
var centerShift_y = (canvas.height - img.height * ratio) / 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, img.width, img.height,
centerShift_x, centerShift_y, img.width * ratio, img.height * ratio);
if (circles) {
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
drawCircle(circle);
ctx.fillStyle = circle.fill;
ctx.fill();
ctx.stroke();
}
}
}
function drawCircle(circle) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.beginPath();
ctx.fillStyle = circle.fill;
ctx.strokeStyle = "black";
ctx.font = "20px Georgia";
ctx.lineWidth = 10;
ctx.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#ffffff";
ctx.fill();
}
function updateCanvas(){
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
let img = new Image();
img.onload = drawImageScaled(img);
img.setAttribute('crossOrigin', 'anonymous');
img.src = "https://images.unsplash.com/photo-1532619675605-1ede6c2ed2b0?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80"
}
function addEventsToCanvas(){
// listen for mouse events
canvas.onmousedown = handleMouseDown;
canvas.onmouseup = handleMouseUp;
canvas.onmousemove = handleMouseMove;
}
function handleMouseDown(e){
// tell the browser we're handling this mouse event
e.preventDefault();
e.stopPropagation();
let mouseX = parseInt(e.clientX - offsetX);
let mouseY = parseInt(e.clientY - offsetY);
// mousedown stuff here
lastX = mouseX;
lastY = mouseY;
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
var dx = circle.x - mouseX;
var dy = circle.y - mouseY;
if (dx * dx + dy * dy < circle.r * circle.r) {
circles[i].isDragging = true;
mouseIsDown = true;
}
}
}
function handleMouseUp(e){
// tell the browser we're handling this mouse event
e.preventDefault();
e.stopPropagation();
// mouseup stuff here
mouseIsDown = false;
for (var i = 0; i < circles.length; i++) {
circles[i].isDragging = false;
}
}
function handleMouseMove(e){
if (!mouseIsDown) {
return;
}
// tell the browser we're handling this mouse event
e.preventDefault();
e.stopPropagation();
let mouseX = parseInt(e.clientX - offsetX);
let mouseY = parseInt(e.clientY - offsetY);
// mousemove stuff here
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
if (circle.isDragging) {
//move
circle.x += (mouseX - lastX);
circle.y += (mouseY - lastY);
}
}
lastX = mouseX;
lastY = mouseY;
updateCanvas()
}
This is the output
To stop drag and drop outside the canvas you simply need to handle the mouseLeave event. From your example i updated your addEventsToCanvas function like this :
function addEventsToCanvas(){
// listen for mouse events
canvas.onmousedown = handleMouseDown;
canvas.onmouseup = handleMouseUp;
canvas.onmousemove = handleMouseMove;
canvas.onmouseleave = function(event) {mouseIsDown = false;}
}
Then for collisions it is just some maths at drawing time. Here is the code to handle collisions with canvas border and others circle :
function drawCircle(circle) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.beginPath();
ctx.fillStyle = circle.fill;
ctx.strokeStyle = "black";
ctx.font = "20px Georgia";
ctx.lineWidth = 10;
//avoid outside canvas x and y
circle.x = Math.min(circle.x, canvas.width-circle.r);
circle.x = Math.max(circle.x, circle.r);
circle.y = Math.min(circle.y, canvas.height-circle.r);
circle.y = Math.max(circle.y, circle.r);
//then check if circles are not too close
if(circle.isDragging) {
circles.forEach(function(c) {
if(c!=circle) {
//calculate distance
let dist = Math.sqrt(Math.pow(Math.abs(c.x-circle.x), 2) + Math.pow(Math.abs(c.y-circle.y), 2));
if(dist<circle.r*2) {
let angle = Math.atan2(c.y - circle.y, c.x - circle.x);
circle.x = c.x - Math.cos(angle)*circle.r*2;
circle.y = c.y - Math.sin(angle)*circle.r*2;
}
}
});
}
ctx.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#ffffff";
ctx.fill();
}
You can test the whole code on JSFiddle
EDIT : I also modified the mouse move handler by replacing
circle.x += (mouseX - lastX);
circle.y += (mouseY - lastY);
by
circle.x = mouseX;
circle.y = mouseY;
It permit to place the circle center at mouse position everytime it is possible.

how to draw control on canvas using mobile touch while having scroll on screen

I'm trying to create a mobile app in which user can draw multiple controls (rectangle) on a canvas.
It works fine if canvas fits the screen, but the controls are not drawn well when I increase the canvas height and width out of the screen (a scroll appear when i increase the size of canvas more than the size of mobile screen).
How to fix this in scroll mode?
Use Chrome Inspect mobile view (iPhone 6/7/8) to test this code
Use Pointer drag to draw controls
<html>
<body>
<canvas id="canvas_transparent" height="3000" width="1000" style="border: 1px solid black;"></canvas>
</body>
</html>
<script type="text/javascript">
var canvas = document.getElementById("canvas_transparent");
var ctx = canvas.getContext("2d");
var endX;
var startX;
var endY;
var startY;
var positions = [];
//--------------------------------------------------------
canvas.addEventListener("touchstart", function(e) {
mousePos = getTouchPos(canvas, e);
startX = mousePos.x;
startY = mousePos.y;
var touch = e.touches[0];
var mouseEvent = new MouseEvent("mousedown", {
clientX: touch.clientX,
clientY: touch.clientY
});
canvas.dispatchEvent(mouseEvent);
}, false);
//---------------------------------------------------------
canvas.addEventListener("touchend", function(e) {
positions.push({
s_x: startX,
s_y: startY,
e_x: endX,
e_y: endY
});
drawAllControls();
console.log(positions);
var mouseEvent = new MouseEvent("mouseup", {});
canvas.dispatchEvent(mouseEvent);
}, false);
//---------------------------------------------------------
canvas.addEventListener("touchmove", function(e) {
var touch = e.touches[0];
endX = touch.clientX;
endY = touch.clientY;
drawSquare()
var mouseEvent = new MouseEvent("mousemove", {
clientX: touch.clientX,
clientY: touch.clientY
});
canvas.dispatchEvent(mouseEvent);
}, false);
//--------------------------------------------------------
// Get the position of a touch relative to the canvas
function getTouchPos(canvasDom, touchEvent) {
var rect = canvasDom.getBoundingClientRect();
return {
x: touchEvent.touches[0].clientX - rect.left,
y: touchEvent.touches[0].clientY - rect.top
};
}
//--------------------------------------------------------
function drawSquare() {
// creating a square
var w = endX - startX;
var h = endY - startY;
var offsetX = (w < 0) ? w : 0;
var offsetY = (h < 0) ? h : 0;
var width = Math.abs(w);
var height = Math.abs(h);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(startX + offsetX, startY + offsetY, width, height);
ctx.fillStyle = "#222222";
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
//----------------------------------------------------------
function drawAllControls() {
for (var i = 0; i < positions.length; i++) {
var w = positions[i].e_x - positions[i].s_x;
var h = positions[i].e_y - positions[i].s_y;
var offsetX = (w < 0) ? w : 0;
var offsetY = (h < 0) ? h : 0;
var width = Math.abs(w);
var height = Math.abs(h);
ctx.beginPath();
ctx.rect(positions[i].s_x + offsetX, positions[i].s_y + offsetY, width, height);
ctx.fillStyle = "#222222";
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
}
</script>
clientX/clientY do not factor in scroll offset. I think you want pageX/pageY
https://developer.mozilla.org/en-US/docs/Web/API/Touch
// Get the position of a touch relative to the canvas
function getTouchPos(canvasDom, touchEvent) {
var rect = canvasDom.getBoundingClientRect();
return {
x: touchEvent.touches[0].pageX - rect.left,
y: touchEvent.touches[0].pageY - rect.top
};
}
Hi i think this will resolve your problem. Put this in the head tag
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
working link page

Have to create an array of objects for different tools to do undo/redo operations using javascript

for circle tool
var circle_array = {"object_Name":"circle", "object_ID":"" , "start_x":"mouse.x" , "start_y":"mouse.y", "start_radius":"mouse.x" };
var onCirclePaint = function () {
// Tmp canvas is always cleared up before drawing.
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
var x = (mouse.x + start_mouse.x) / 2;
var y = (mouse.y + start_mouse.y) / 2;
var radius = Math.max(
Math.abs(mouse.x - start_mouse.x),
Math.abs(mouse.y - start_mouse.y)) / 2;
tmp_ctx.beginPath();
tmp_ctx.arc(x, y, radius, 0, Math.PI * 2, false);
// tmp_ctx.arc(x, y, 5, 0, Math.PI*2, false);
tmp_ctx.strokeStyle = $('#selColor').val();
tmp_ctx.lineWidth = $('#selWidth').val();
tmp_ctx.stroke();
tmp_ctx.closePath();
circle_array.start_x = mouse.x;
circle_array.start_y = mouse.y;
circle_array.start_radius = mouse.x;
};
for line tool
var line_Array = {"object_Name":"Line", "object_ID":"", "start_x":"start_mouse.x", "start_y":"start_mouse.y", "end_x":"mouse.x", "end_y":"mouse.y"};
var onLinePaint = function () {
tmp_ctx.lineWidth = $('#selWidth').val();
tmp_ctx.lineJoin = 'round';
tmp_ctx.lineCap = 'round';
tmp_ctx.strokeStyle = $('#selColor').val();
tmp_ctx.fillStyle = $('#selColor').val();
// Tmp canvas is always cleared up before drawing.
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
tmp_ctx.beginPath();
tmp_ctx.moveTo(start_mouse.x, start_mouse.y);
tmp_ctx.lineTo(mouse.x, mouse.y);
tmp_ctx.stroke();
tmp_ctx.closePath();
// line_Array.object_ID =start_mouse.y;
line_Array.start_x =start_mouse.x;
line_Array.start_y =start_mouse.y;
line_Array.end_x = mouse.x;
line_Array.end_y = mouse.y;
console.log('this :'+start_mouse.x);
};
for rectangle tool
var Rectangle_Array = {"object_Name":"Rectangle", "object_ID":"", "start_x":"mouse.x", "start_y":"mouse.y", "start_width":"mouse.x", "start_height":"mouse.y" };
var onRectPaint = function () {
tmp_ctx.lineWidth = $('#selWidth').val();
tmp_ctx.lineJoin = 'round';
tmp_ctx.lineCap = 'round';
tmp_ctx.strokeStyle = $('#selColor').val();
tmp_ctx.fillStyle = $('#selColor').val();
// Tmp canvas is always cleared up before drawing.
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
var x = Math.min(mouse.x, start_mouse.x);
var y = Math.min(mouse.y, start_mouse.y);
var width = Math.abs(mouse.x - start_mouse.x);
var height = Math.abs(mouse.y - start_mouse.y);
tmp_ctx.strokeRect(x, y, width, height);
Rectangle_Array.start_x =mouse.x;
Rectangle_Array.start_y =mouse.y;
Rectangle_Array.start_width = mouse.x;
Rectangle_Array.start_height = mouse.y;
console.log('this :'+mouse.x);
};
i created an array for each tool but i need to create an array of objects and do the undo/redo functions using push pop method for above code .please someone help me.
i created an array for each tool but i need to create an array of objects and do the undo/redo functions using push pop method for above code .please someone help me

EaselJS Alpha Mask Filter

I'm fairly new to Canvas. I've been trying to get the images reversed in this EaselJS Alpha Mask example so that the initial image is clear, and what you paint is blurry; basically, the reverse of the demo.
I've been playing around with it for hours, applying filters to the bitmap var and removing them from the blur var. Everything I do just doesn't work. Seems like it would be an easy fix of just switching things around but that doesn't seem to be the case. Not for me anyway.
Does anybody have an example of this, or know what to do? I could provide code examples of what I did, but it's basically just playing around with stuff like a monkey on a typewriter.
Here's the code on Github
Here's the relevant code from their example.
<script id="editable">
var stage;
var isDrawing;
var drawingCanvas;
var oldPt;
var oldMidPt;
var displayCanvas;
var image;
var bitmap;
var maskFilter;
var cursor;
var text;
var blur;
function init() {
examples.showDistractor();
image = new Image();
image.onload = handleComplete;
image.src = "../_assets/art/flowers.jpg";
stage = new createjs.Stage("testCanvas");
//text = new createjs.Text("Loading...", "20px Arial", "#FFF");
//text.set({x: stage.canvas.width / 2, y: stage.canvas.height - 40});
//text.textAlign = "center";
}
function handleComplete() {
examples.hideDistractor();
createjs.Touch.enable(stage);
stage.enableMouseOver();
stage.addEventListener("stagemousedown", handleMouseDown);
stage.addEventListener("stagemouseup", handleMouseUp);
stage.addEventListener("stagemousemove", handleMouseMove);
drawingCanvas = new createjs.Shape();
bitmap = new createjs.Bitmap(image);
blur = new createjs.Bitmap(image);
blur.filters = [new createjs.BlurFilter(24, 24, 2), new createjs.ColorMatrixFilter(new createjs.ColorMatrix(60))];
blur.cache(0, 0, 960, 400);
//text.text = "Click and Drag to Reveal the Image.";
stage.addChild(blur, text, bitmap);
updateCacheImage(false);
cursor = new createjs.Shape(new createjs.Graphics().beginFill("#FFFFFF").drawCircle(0, 0, 25));
cursor.cursor = "pointer";
stage.addChild(cursor);
}
function handleMouseDown(event) {
oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
oldMidPt = oldPt;
isDrawing = true;
}
function handleMouseMove(event) {
cursor.x = stage.mouseX;
cursor.y = stage.mouseY;
if (!isDrawing) {
stage.update();
return;
}
var midPoint = new createjs.Point(oldPt.x + stage.mouseX >> 1, oldPt.y + stage.mouseY >> 1);
drawingCanvas.graphics.setStrokeStyle(40, "round", "round")
.beginStroke("rgba(0,0,0,0.2)")
.moveTo(midPoint.x, midPoint.y)
.curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y);
oldPt.x = stage.mouseX;
oldPt.y = stage.mouseY;
oldMidPt.x = midPoint.x;
oldMidPt.y = midPoint.y;
updateCacheImage(true);
}
function handleMouseUp(event) {
updateCacheImage(true);
isDrawing = false;
}
function updateCacheImage(update) {
if (update) {
drawingCanvas.updateCache();
} else {
drawingCanvas.cache(0, 0, image.width, image.height);
}
maskFilter = new createjs.AlphaMaskFilter(drawingCanvas.cacheCanvas);
bitmap.filters = [maskFilter];
if (update) {
bitmap.updateCache(0, 0, image.width, image.height);
} else {
bitmap.cache(0, 0, image.width, image.height);
}
stage.update();
}
</script>
The pure Javascript way using the Canvas 2D context API.
You will need to create a canvas, load the image, create a mask image, and a blur image. I have blurred the image already as I did not want to write a blur.
The following functions in the object imageTools create the canvas/images, and loads images. Note that the canvas and images are interchangeable. The canvas does not have a src, and an image can not be drawn on appart from that they are the same. I convert all images to canvas and attach the context to them. I also call them images.
/** ImageTools.js begin **/
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
loadImage : function (url, callback) {
var image = new Image();
image.src = url;
image.addEventListener('load', callback);
image.addEventListener('error', callback);
return image;
}
};
return tools;
})();
Then I use imageTools to load the images I need and create a mask, when I have the image size as I am matching the mask resolution to the image resolution
// load the images and create the mask
var imageLoadedCount = 0;
var error = false;
var maskImage;
var flowerImage = imageTools.loadImage("http://www.createjs.com/demos/_assets/art/flowers.jpg", function (event) {
if (event.type === "load") {
imageLoadedCount += 1;
} else {
error = true;
}
});
var flowerImageBlur = imageTools.loadImage("http://i.stack.imgur.com/3S5m8.jpg", function () {
if (event.type === "load") {
maskImage = imageTools.createImage(this.width, this.height);
imageLoadedCount += 1;
} else {
error = true;
}
});
I use requestAnimationFrame to create a 60FPS canvas drawing function that waits for the images to load and then displays the 3 layers onto the canvas
// ctx is the main canvas context.
// drawImageCentered scales the image to fit. See Demo for code.
// draw the unblured image that will appear at the top
ctx.globalCompositeOperation = "source-over";
drawImageCentered(ctx, flowerImage, cw, ch);
drawText(ctx, "Click drag to blur the image via mask", 40 + Math.sin(time / 100), cw, ch - 30, "White");
// Mask out the parts when the mask image has pixels
ctx.globalCompositeOperation = "destination-out";
drawImageCentered(ctx, maskImage, cw, ch);
// draw the blured image only where the destination has been masked
ctx.globalCompositeOperation = "destination-atop";
drawImageCentered(ctx, flowerImageBlur, cw, ch);
It first draws the image that appears if no masked pixels are visible. Then it draw some text for instructions.
Next comes the mask that uses destination-out. This means that for pixels in the mask that have an alpha > 0 remove from the destination that amount of alpha. So if a mask pixel has an alpha of 50 and the destination (canvas) has an alpha of 255 then the result of that pixel after rendering the mask with destination-out will be 255 - 50 = 205. This effectively has put holes on the canvas where ever there are pixels on the mask.
Now we can fill the holes with the blurred image and render it using destination-atop which means only draw pixels from the source (blurred image) where the destination alpha is less that 255
That is the layered masking done, all we need is to draw on the mask. For that we just listen to the mouse events and if the button is down draw a circle on the mask where the mouse is. My example has scaled the images so there is a little extra work there but the basics are as follows,
// draws circle with gradient
function drawCircle(ctx, x, y, r) {
var gr = ctx.createRadialGradient(x, y, 0, x, y, r)
gr.addColorStop(1, "rgba(0,0,0,0)")
gr.addColorStop(0.5, "rgba(0,0,0,0.08)")
gr.addColorStop(0, "rgba(0,0,0,0.1)")
ctx.fillStyle = gr;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
// draw a circle on the mask where the mouse is.
drawCircle(maskImage.ctx, mouse.x, mouse.y, 20);
For the demo there is a little more code to make it all work nicely but you can pick out the bits you need.
var imageLoadedCount = 0;
var error = false;
var maskImage;
var flowerImage;
var flowerImageBlur;
/** ImageTools.js begin **/
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
loadImage : function (url, callback) {
var image = new Image();
image.src = url;
image.addEventListener('load', callback);
image.addEventListener('error', callback);
return image;
}
};
return tools;
})();
var mouse;
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** MouseFull.js begin **/
if(typeof mouse !== "undefined"){ // if the mouse exists
if( mouse.removeMouse !== undefined){
mouse.removeMouse(); // remove previouse events
}
}else{
var mouse;
}
var canvasMouseCallBack = undefined; // if needed
mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
startMouse:undefined,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true;
} else if (t === "mousewheel") { m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") { m.w = -e.detail;}
if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
e.preventDefault();
}
function startMouse(element){
if(element === undefined){
element = document;
}
mouse.element = element;
mouse.mouseEvents.forEach(
function(n){
element.addEventListener(n, mouseMove);
}
);
element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
}
mouse.removeMouse = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(
function(n){
mouse.element.removeEventListener(n, mouseMove);
}
);
canvasMouseCallBack = undefined;
}
}
mouse.mouseStart = startMouse;
return mouse;
})();
if(typeof canvas !== "undefined"){
mouse.mouseStart(canvas);
}else{
mouse.mouseStart();
}
/** MouseFull.js end **/
// load the images and create the mask
if(imageLoadedCount === 0){
imageLoadedCount = 0;
error = false;
maskImage;
flowerImage = imageTools.loadImage("http://www.createjs.com/demos/_assets/art/flowers.jpg", function (event) {
if (event.type === "load") {
imageLoadedCount += 1;
} else {
error = true;
}
})
flowerImageBlur = imageTools.loadImage("http://i.stack.imgur.com/3S5m8.jpg", function () {
if (event.type === "load") {
maskImage = imageTools.createImage(this.width, this.height);
imageLoadedCount += 1;
} else {
error = true;
}
})
}
// set up the canvas
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
// calculate time to download image using the MS algorithum. As this code is a highly gaurded secret I have obsficated it for your personal safty.
var calculateTimeToGo= (function(){var b="# SecondQMinuteQHourQDayQWeekQMonthQMomentQTick#.,Some time soon,Maybe Tomorrow.".replace(/Q/g,"#.,# ").split(","),r=Math.random,f=Math.floor,lc=0,pc=0,lt=0,lp=0;var cttg=function(a){if(lc===0){lc=100+r(r()*60);lt=f(r()*40);if(pc===0||r()<(lp/b.length)-0.2){lp=f(r()*b.length);pc=1+f(r()*10)}else{pc-=1}}else{lc-=1}a=lt;if(lp===0){a=lt;if(r()<0.01){lt-=1}}var s=b[lp].replace("#",a);if(a===1){s=s.replace("#","")}else{s=s.replace("#","s")}return s};return cttg})();
// draws circle with gradient
function drawCircle(ctx, x, y, r) {
var gr = ctx.createRadialGradient(x, y, 0, x, y, r)
gr.addColorStop(1, "rgba(0,0,0,0)")
gr.addColorStop(0.5, "rgba(0,0,0,0.08)")
gr.addColorStop(0, "rgba(0,0,0,0.1)")
ctx.fillStyle = gr;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
// draw text
function drawText(ctx, text, size, x, y, c) {
ctx.fillStyle = c;
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.lineJoin = "round";
ctx.font = size + "px Arial Black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
if (c !== "black") {
ctx.strokeText(text, x, y + 1);
}
ctx.fillText(text, x, y);
}
// draw the image to fit the current canvas size
function drawImageCentered(ctx, image, x, y) {
var scale = Math.min(w / image.width, h / image.height);
ctx.setTransform(scale, 0, 0, scale, cw, ch);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
// points for filling gaps between mouse moves.
var lastMX,lastMY;
// update function will try 60fps but setting will slow this down.
function update(time){
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore transform
ctx.clearRect(0, 0, w, h); // clear rhe canvas
// have the images loaded???
if (imageLoadedCount === 2) {
// draw the unblured image that will appear at the top
ctx.globalCompositeOperation = "source-over";
drawImageCentered(ctx, flowerImage, cw, ch);
drawText(ctx, "Click drag to blur the image via mask", 20 + Math.sin(time / 100), cw, ch - 30, "White");
// Mask out the parts when the mask image has pixels
ctx.globalCompositeOperation = "destination-out";
drawImageCentered(ctx, maskImage, cw, ch);
// draw the blured image only where the destination has been masked
ctx.globalCompositeOperation = "destination-atop";
drawImageCentered(ctx, flowerImageBlur, cw, ch);
// is the mouse down
if (mouse.buttonRaw === 1) {
// because image has been scaled need to get mouse coords on image
var scale = Math.min(w / flowerImage.width, h / flowerImage.height);
var x = (mouse.x - (cw - (maskImage.width / 2) * scale)) / scale;
var y = (mouse.y - (ch - (maskImage.height / 2) * scale)) / scale;
// draw circle on mask
drawCircle(maskImage.ctx, x, y, 20);
// if mouse is draging then draw some points between to fill the gaps
if (lastMX !== undefined) {
drawCircle(maskImage.ctx, ((x + lastMX) / 2 + x) / 2, ((y + lastMY) / 2 + y) / 2, 20);
drawCircle(maskImage.ctx, (x + lastMX) / 2, (y + lastMY) / 2, 20);
drawCircle(maskImage.ctx, ((x + lastMX) / 2 + lastMX) / 2, ((y + lastMY) / 2 + lastMY) / 2, 20);
}
// save las mouse pos on image
lastMX = x;
lastMY = y;
} else {
// undefined last mouse pos
lastMX = undefined;
}
} else {
// Laoding images so please wait.
drawText(ctx, "Please wait.", 40 + Math.sin(time / 100), cw, ch - 30, "White");
drawText(ctx, "loading images... ", 12, cw, ch, "black")
drawText(ctx, "ETA " + calculateTimeToGo(time), 14, cw, ch + 20, "black")
}
// if not restart the request animation frame
if(!STOP){
requestAnimationFrame(update);
}else{
var can = document.getElementById("canv");
if(can !== null){
document.body.removeChild(can);
}
STOP = false;
}
}
update();
}
var STOP = false; // flag to tell demo app to stop
function resizeEvent() {
var waitForStopped = function () {
if (!STOP) { // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped, 200);
}
STOP = true;
setTimeout(waitForStopped, 100);
}
window.addEventListener("resize", resizeEvent);
demo();
/** FrameUpdate.js end **/
There are a few steps to do this. Most of them you have probably already done:
1) Change the order you add the items to stage. Since you want to reveal the blur instead, add them in the reverse order. This puts the blur on top.
stage.addChild(bitmap, text, blur);
2) Change what is cached or updateCached in the updateCacheImage method:
if (update) {
blur.updateCache(0, 0, image.width, image.height);
} else {
blur.cache(0, 0, image.width, image.height);
}
This is where you probably got tripped up. If you set the filters on the blurImage to just the maskFilter, it will not appear to work. The maskFilter IS working, but will have removed the blur and color filters that were applied. To add the maskFilter, you have to put it in the array with the current filters. This is my approach, which ensures the original 2 filters are intact, and the maskFilter is just added once:
blur.filters.length = 2; // Truncate the array to 2
blur.filters.push(maskFilter); // add the new filter
In my opinion, this effect isn't as obvious - so you might want to increase the opacity of the brush:
drawingCanvas.graphics.setStrokeStyle(40, "round", "round")
.beginStroke("rgba(0,0,0,0.5)"); // From 0.2
I was the author of the original AlphaMaskFilter demo in EaselJS - glad you found it useful and/or interesting!

Limit values between two points on an arc?

I'm trying to adapt the code from a previous question on circular dial controls. The concept is pretty similar to this one, except I would like to define a range in which the dial cannot be selected. Consider the volume controls/dials in hardware; they often have these 'dead zones' where they can't be turned:
How can I replicate this in JavaScript? Here's the adapted code so far:
function Dial(size) {
var dial = this;
var canvas = document.querySelector('#c');
var ctx = canvas.getContext("2d");
var pi2 = Math.PI*2;
this.from = 0.75 * Math.PI;
this.to = 0.25 * Math.PI;
this.value = this.from;
var radius = size / 2 - 10;
this.draw = function() {
ctx.save();
ctx.clearRect(0,0,size,size);
ctx.translate(size/2,size/2);
ctx.beginPath();
ctx.strokeStyle = "silver";
ctx.lineWidth = 2;
ctx.arc(0, 0, radius, this.from, this.to);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
ctx.arc(-radius*Math.sin(this.value),
-radius*Math.cos(this.value),
8, 0, 2*Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
};
var getMousePos = function(canvas, evt) {
return {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
};
};
var inBounds = function(pos) {
return Math.hypot(
size / 2 - radius * Math.sin(dial.value) - pos.x,
size / 2 - radius * Math.cos(dial.value) - pos.y
) <= 8;
};
canvas.addEventListener("mousemove", function(evt) {
var pos = getMousePos(canvas, evt);
if (dial.markerMoving) {
if (pos.x == size/2 && pos.y == size/2)
return;
dial.value = Math.atan2(size/2-pos.x,size/2-pos.y);
}
dial.draw();
}, false);
canvas.addEventListener("mousedown", function(evt) {
var pos = getMousePos(canvas, evt);
dial.markerMoving = inBounds(pos);
}, false);
canvas.addEventListener("mouseup", function(evt) {
dial.markerMoving = false;
}, false);
this.draw();
};
new Dial(150);
<canvas id="c"></canvas>
Bonus points if you can work out how to display a 'range' on the selection - from the starting point on the dial to the selection point.
Turns out it was pretty straightforward, using Math.abs.
function Dial(size) {
var dial = this;
var canvas = document.querySelector('#c');
var ctx = canvas.getContext("2d");
var pi2 = Math.PI*2;
this.from = 0.75 * Math.PI;
this.to = 0.25 * Math.PI;
this.value = this.from;
var radius = size / 2 - 10;
this.draw = function() {
ctx.save();
ctx.clearRect(0,0,size,size);
ctx.translate(size/2,size/2);
ctx.beginPath();
ctx.strokeStyle = "silver";
ctx.lineWidth = 2;
ctx.arc(0, 0, radius, this.from, this.to);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
ctx.arc(-radius*Math.sin(this.value),
-radius*Math.cos(this.value),
8, 0, 2*Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
};
var getMousePos = function(canvas, evt) {
return {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
};
};
var inBounds = function(pos) {
return Math.hypot(
size / 2 - radius * Math.sin(dial.value) - pos.x,
size / 2 - radius * Math.cos(dial.value) - pos.y
) <= 8;
};
canvas.addEventListener("mousemove", function(evt) {
var pos = getMousePos(canvas, evt);
if (dial.markerMoving) {
if (pos.x == size/2 && pos.y == size/2) {
return;
}
var radians = Math.atan2(size/2-pos.x,size/2-pos.y);
if (Math.abs(radians) < dial.from) {
dial.value = radians;
dial.draw();
}
}
}, false);
canvas.addEventListener("mousedown", function(evt) {
var pos = getMousePos(canvas, evt);
dial.markerMoving = inBounds(pos);
}, false);
canvas.addEventListener("mouseup", function(evt) {
dial.markerMoving = false;
}, false);
this.draw();
};
new Dial(150);
<canvas id="c"></canvas>

Categories

Resources