EaselJS Alpha Mask Filter - javascript
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!
Related
Detecting circle intersection from array
I have a canvas on which I can place circles wherever I click. I want to detect when any two circle intersect, so i am storing my coordinates in an array. The radius of every circle is 30, so that is just hardcoded into my formula. That said, even when I place two on top of each other, it's not triggering my little filltext to let me know that it's working. I've tried many things. If someone could tell me why this isn't working, that would be appreciable. The parts where I place the dots works just fine; I just need to detect overlap. window.onload = init; function init() { var canvas = document.getElementById("testCanvas"); var context = canvas.getContext("2d"); var newPath = false; var circles = []; canvas.onmousedown = function(e) { newPath = true; x = e.clientX - e.target.offsetLeft; y = e.clientY - e.target.offsetTop; context.moveTo(x, y); context.beginPath(); context.arc(x, y, 30, 0, 2 * Math.PI, true); var nextColor = randomColor(); context.fillStyle = nextColor; context.fill(); var aCircle = [x, y]; function isIntersect(aCircle, circle) { return Math.sqrt((aCircle[0]-circle.x) ** 2 + (aCircle[1] - circle.y) ** 2) < 30; }; circles.forEach(circle => { if (isIntersect(aCircle, circle)) { context.fillText('INTERSECTED', 60, 160); } }); circles.push(aCircle); context.closePath(); } }
Multiply the radius by 2 since each circle has one... window.onload = init; function init() { var canvas = document.getElementById("testCanvas"); var context = canvas.getContext("2d"); var newPath = false; var circles = []; canvas.onmousedown = function(e) { newPath = true; x = e.clientX - e.target.offsetLeft; y = e.clientY - e.target.offsetTop; context.moveTo(x, y); context.beginPath(); context.arc(x, y, 30, 0, 2 * Math.PI, true); var nextColor = '#123123' //randomColor(); context.fillStyle = nextColor; context.fill(); var aCircle = [x, y]; function isIntersect(aCircle, circle) { var radius = 30; var dist = Math.hypot(aCircle[0]-circle[0], aCircle[1]-circle[1]); return dist <= (radius * 2) }; circles.forEach(circle => { if (isIntersect(aCircle, circle)) { console.log("intresected"); //context.fillText('INTERSECTED', 0, 0); } }); circles.push(aCircle); context.closePath(); } } <canvas id='testCanvas'></canvas>
Image as a stencil for html canvas
I'm trying to have a user manually color in certain parts of an image. As an example, here's a cat https://techflourish.com/images/3-cat-clipart-9.png. The user should be able to color in the foot of the cat if they choose to. I want them to only color inside the cat body image portion of the canvas (not the background portion of the image or whitespace of canvas, but I guess I could just manually trim the image). What I've attempted so far is below. Basically I check the color of the pixel at my position and draw only if it isn't that background color. This sort of works, but I'm able to bleed out really easily because something is off. I was wondering if it was possibly to set a specific clip area, but wasn't able to figure it out. ` var canvas = document.getElementById("space"); var ctx = canvas.getContext("2d"); var pos = { x: 0, y: 0 }; // new position from mouse events function setPosition(e) { pos.x = e.clientX; pos.y = e.clientY; } function rgbToHex(r, g, b) { if (r > 255 || g > 255 || b > 255) throw "Invalid color component"; return ((r << 16) | (g << 8) | b).toString(16); } function draw(e) { if (e.buttons !== 1) return; // if mouse is pressed..... var color = "#cb3594"; ctx.beginPath(); // begin the drawing path ctx.lineWidth = 5; // width of line ctx.lineCap = "round"; // rounded end cap ctx.strokeStyle = color; // hex color of line var p = ctx.getImageData(pos.x, pos.y, 1, 1).data; var sourceColor = rgbToHex(p[0], p[1], p[2]); if(sourceColor != "BACKGROUNDHEX" && sourceColor != color) { ctx.moveTo(pos.x, pos.y); // from position setPosition(e); p = ctx.getImageData(pos.x, pos.y, 1, 1).data; targetColor = rgbToHex(p[0], p[1], p[2]); if(targetColor != "BACKGROUNDHEX" && targetColor != color) { ctx.lineTo(pos.x, pos.y); // to position ctx.stroke(); // draw it! } } } var outlineImage = new Image(); outlineImage.onload = function() { ctx.drawImage(outlineImage, 0, 0, 704, 720); } outlineImage.src = "IMAGE.png"; space.addEventListener("mousemove", draw); space.addEventListener("mousedown", setPosition); space.addEventListener("mouseenter", setPosition); </script> ` (related edit: the bleeding is caused by my "sourceColor != color" being wrong, but the question is still relevant as this still doesn't feel like a great solution)
Since the parts of the image you don't want to color are transparent, you can set the context's globalCompositeOperation to 'source-atop'. After that, any pixels you draw to the canvas will automatically take on the overwritten pixels' opacity, and you don't have to mess with getImageData: var canvas = document.getElementById("space"); var ctx = canvas.getContext("2d"); var pos = { x: 0, y: 0 }; // new position from mouse events function setPosition(e) { // offsetX/Y gives the correct coordinates within the canvas // assuming it has no padding pos.x = e.offsetX; pos.y = e.offsetY; } function draw(e) { if (e.buttons !== 1) return; // if mouse is pressed..... var color = "#cb3594"; ctx.beginPath(); // begin the drawing path ctx.lineWidth = 5; // width of line ctx.lineCap = "round"; // rounded end cap ctx.strokeStyle = color; // hex color of line ctx.moveTo(pos.x, pos.y); // from position setPosition(e); ctx.lineTo(pos.x, pos.y); // to position ctx.stroke(); // draw it! } var outlineImage = new Image(); outlineImage.onload = function() { // the default, set explicitly because we're changing it elsewhere ctx.globalCompositeOperation = 'source-over'; ctx.drawImage(outlineImage, 0, 0); // don't draw over the transparent parts of the canvas ctx.globalCompositeOperation = 'source-atop'; // wait until the stencil is loaded before handing out crayons space.addEventListener("mousemove", draw); space.addEventListener("mousedown", setPosition); space.addEventListener("mouseenter", setPosition); } outlineImage.src = "https://i.stack.imgur.com/so095.png"; <canvas id="space" width="610" height="733"></canvas>
Pixelated resize using canvas with transparent PNG
I want to accomplish a pixelated effect using the canvas option imageSmoothingEnabled=false; so the image "unblurs" on scroll. Everything works fine until using transparent images namely PNGs. The scaled image is projected, which stays in the background. Also the image does not get loaded until the user has scrolled a few pixels. I've found out that the canvas.drawImage() function owns parameters to set the offset. However I haven't found a solution to this. Demo https://jsfiddle.net/aLjfemru/ var ctx = canvas.getContext('2d'), img = new Image(), play = false; /// turn off image smoothing - this will give the pixelated effect ctx.mozImageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false; /// wait until image is actually available img.onload = function(){ image1.src="nf.png"; context.drawImage(image1, 50, 50, 10, 10); }; img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png'; /// MAIN function function pixelate(v) { document.getElementById("v").innerHTML = "(v): " + v; /// if in play mode use that value, else use slider value var size = v * 0.01; var w = canvas.width * size; var h = canvas.height * size; /// draw original image to the scaled size ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, w, h); ctx.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height); } function onScroll() { $(window).on('scroll', function() { var y = window.pageYOffset; if (y > 10) { y = Math.pow(y, 0.8); if (y >= 60) { y = 100; } pixelate(y); } }); } onScroll();
Some quick changes to get it happening Use a second canvas to do the pixelation Wait for the images to load before doing the rendering. The onscroll will not fire until you scroll, so when image has loaded call the rendering function to display the image. canvas.width = innerWidth-20; ctx = canvas.getContext("2d"); var ctxImage; const img = new Image; img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png'; /// wait until image is actually available img.onload = function(){ // I dont knwo what this is for so removed the following two lines //image1.src="nf.png"; //context.drawImage(image1, 50, 50, 10, 10); // Create a canvas to match the image var c = document.createElement("canvas"); canvas.width = Math.min(canvas.width,(c.width = this.naturalWidth)); canvas.height = c.height = this.naturalHeight; ctxImage = c.getContext("2d"); // changing canvas size resets the state so need to set this again. ctx.imageSmoothingEnabled = false; onScroll(); pixelate(100); // call first time }; ctx.font = "32px arial"; ctx.textAlign = "center"; ctx.fillText("Loading please wait.",ctx.canvas.width /2, ctx.canvas.height / 4); /// MAIN function function pixelate(v) { document.getElementById("v").innerHTML = "(v): " + v; /// if in play mode use that value, else use slider value var size = Number(v) * 0.01; var w = img.width * size; var h = img.height * size; ctx.clearRect(0, 0, canvas.width, canvas.height); ctxImage.clearRect(0, 0, ctxImage.canvas.width, ctxImage.canvas.height); ctxImage.drawImage(img, 0, 0, w, h); ctx.drawImage(ctxImage.canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height); } function onScroll() { addEventListener("scroll", function() { var y = window.pageYOffset; if (y > 10) { y = Math.pow(y, 0.65); if (y >= 100) { y = 100; } pixelate(y); } }); } #fix { position: fixed; } html { height: 2000px; } <div id="fix"> <p id="v" value="Animate">1</p><br /> <canvas id="canvas"></canvas> </div>
This has since been made into an extremely minimalist library, and my PR for PNG support can be found here. Once it has been merged I will come back and update this answer. The full code, generalized and simplified from #Blindman67's answer: /** * 8bit * * A module that converts an image into a pixelated version (just like * 8bit artwork). * * #author rogeriopvl <https://github.com/rogeriopvl> * #license MIT */ (function (root, factory) { if (typeof define === "function" && define.amd) { define([], factory); } else if (typeof exports === "object") { module.exports = factory(); } else { root.eightBit = factory(); } } (this, function () { // Necessary to hide the original image with PNG transparency const invisibleCanvas = document.createElement("canvas"); const invisibleCtx = invisibleCanvas.getContext("2d"); /** * Draws a pixelated version of an image in a given canvas. * #param {object} canvas - a canvas object * #param {object} image - an image HTMLElement object * #param {number} quality - the new quality: between 0 and 100 */ const eightBit = function (canvas, image, quality) { quality /= 100; canvas.width = invisibleCanvas.width = image.width; canvas.height = invisibleCanvas.height = image.height; const scaledW = canvas.width * quality; const scaledH = canvas.height * quality; const ctx = canvas.getContext("2d"); ctx.mozImageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false; // Draws image scaled to desired quality on the invisible canvas, then // draws that scaled image on the visible canvas. ctx.clearRect(0, 0, canvas.width, canvas.height); invisibleCtx.clearRect(0, 0, invisibleCtx.canvas.width, invisibleCtx.canvas.height); invisibleCtx.drawImage(image, 0, 0, scaledW, scaledH); ctx.drawImage(invisibleCtx.canvas, 0, 0, scaledW, scaledH, 0, 0, canvas.width, canvas.height); }; return eightBit; }));
Convert squares to circles in canvas html
OK so I appreciate that this is a massively basic question but I'm totally new to canvas and I just need to do something simple. Basically I am using springy.js to draw force directed graphs. The nodes on the graph are squares and I just want them to be circles. Can someone show me what I should change in the code below and I can figure out the rest from there I tried ctx.arc(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight,2*Math.PI); ctx.stroke(); instead of the line with clearRect but the boxes remain and the connections between boxes stop being straight lines. function drawNode(node, p) { var s = toScreen(p); ctx.save(); // Pulled out the padding aspect sso that the size functions could be used in multiple places // These should probably be settable by the user (and scoped higher) but this suffices for now var paddingX = 6; var paddingY = 6; var contentWidth = node.getWidth(); var contentHeight = node.getHeight(); var boxWidth = contentWidth + paddingX; var boxHeight = contentHeight + paddingY; // clear background ctx.clearRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight); // fill background if (selected !== null && selected.node !== null && selected.node.id === node.id) { ctx.fillStyle = "#FFFFE0"; //when clicked } else if (nearest !== null && nearest.node !== null && nearest.node.id === node.id) { ctx.fillStyle = "#EEEEEE";//when hovered over } else { //if the node.FBScore >10 then ctx.fillStyle = "#F00909"; ctx.fillStyle = "#E34747";//normal colour } ctx.fillRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight); if (node.data.image == undefined) { ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.font = (node.data.font !== undefined) ? node.data.font : nodeFont; ctx.fillStyle = (node.data.color !== undefined) ? node.data.color : "#000000"; var text = (node.data.label !== undefined) ? node.data.label : node.id; ctx.fillText(text, s.x - contentWidth/2, s.y - contentHeight/2); } else { // Currently we just ignore any labels if the image object is set. One might want to extend this logic to allow for both, or other composite nodes. var src = node.data.image.src; // There should probably be a sanity check here too, but un-src-ed images aren't exaclty a disaster. if (src in nodeImages) { if (nodeImages[src].loaded) { // Our image is loaded, so it's safe to draw ctx.drawImage(nodeImages[src].object, s.x - contentWidth/2, s.y - contentHeight/2, contentWidth, contentHeight); } }else{ // First time seeing an image with this src address, so add it to our set of image objects // Note: we index images by their src to avoid making too many duplicates nodeImages[src] = {}; var img = new Image(); nodeImages[src].object = img; img.addEventListener("load", function () { // HTMLImageElement objects are very finicky about being used before they are loaded, so we set a flag when it is done nodeImages[src].loaded = true; }); img.src = src; } } ctx.restore(); }
Instead of replacing the clearRect() method, you should replace the fillRect() with the arc(x, y, radius, startAngle, endAngle, anticlockwise); one : var canvas = document.querySelector('canvas'); var ctx = canvas.getContext('2d'); function drawNode() { var s = { x: (Math.random() * 200) + 50, y: (Math.random() * 200) + 50 }; var paddingX = 6; var paddingY = 6; var contentWidth = s.x / 5; var contentHeight = s.y / 5; var boxWidth = contentWidth + paddingX; var boxHeight = contentHeight + paddingY; ctx.fillStyle = '#AAFFAA'; // I modified it so the whole canvas will be cleared ctx.clearRect(0, 0, canvas.width, canvas.height); // We start a new path ctx.beginPath(); // then we draw our circle, setting its radius to the max between contentWidth and contentHeight ctx.arc(s.x, s.y , Math.max(boxWidth,boxHeight)/2, 0, 2 * Math.PI); // and finally we fill it ctx.fill(); } document.addEventListener('click', drawNode); drawNode(); canvas{cursor:pointer}; <canvas height="300" width="300" />
I have create a simple jsFiddle to show how to draw a curved corner rectangle -- Updated so you can now simply change the roundedValue which will then change the smoothness of the corners. http://jsfiddle.net/gHCJt/1127/ // Get canvas var canvas = $("#canvas"); var context = canvas.get(0).getContext("2d"); // Draw simple rect var rectX = 125; var rectY = 125; var rectWidth = 150; var rectHeight = 150; var roundedValue = 75; // Apply corner context.lineJoin = "round"; context.lineWidth = roundedValue; // Apply the corner to the draw method for strokeRect and fillRect context.strokeRect(rectX+(roundedValue/2), rectY+(roundedValue/2), rectWidth-roundedValue, rectHeight-roundedValue); context.fillRect(rectX+(roundedValue/2), rectY+(roundedValue/2), rectWidth-roundedValue, rectHeight-roundedValue);
Eraser in Canvas HTML 5
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.