Image as a stencil for html canvas - javascript

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>

Related

Add color filter to black and white image with js?

Is it possible to add a color filter to a black and white image?
So for example, lets say I want to use this image as a particle in a game:particle image
Is it possible for me to draw this image with some sort of filter so it can become whatever color i'd like?
Similar to how you can color in a rectangle with whatever color you want:
ctx.fillstyle = "rgb(255, 0, 0)";
ctx.fillrect(0, 0, 100, 100);
is there any way for me to dynamically add a color filter over an image in this way?
(I only want to apply the color to the image not the whole canvas)
You can use your image as a mask. Use ctx.globalCompositeOperation after drawing the mask with "source-in" when you draw the background to "cut" after mask. And "destination-in" if your mask is drawn after the background.
let color = 'rgb(255, 0, 0)';
let image = new Image();
image.src = 'https://i.stack.imgur.com/XFpL3.png';
image.onload = () => {
let canvas = document.createElement('canvas');
let w = canvas.width = image.width;
let h = canvas.height = image.height;
let ctx = canvas.getContext('2d');
ctx.fillStyle = color;
ctx.drawImage(image,0,0);
ctx.globalCompositeOperation = 'source-in';
ctx.fillRect(0,0,w,h);
document.body.append(canvas);
}
Updated
I miss read the question and gave an answer that may not suit your needs. Thus this update demonstrates how to color BW images for particle FX.
Unfortunately the 2D API is not designed with game rendering in mind and for the best results you would implement the rendering via WebGL giving you a huge variety of FX and a massive performance gain. However WebGL requires a lot of code and a good understanding of shaders.
NOTE the image you provided is black. When coloring a black and white image the black will still be black. I will thus assume the image you want colored is white. I will be using a copy of your image that has set the Alpha range from 0 - 255 and the RGB have a circular gradient from black to white.
Sprites
In the game world an image rendered to the display is called a sprite. A sprite is a rectangular image, It has 4 channels RGBA and can be drawn at any position, rotation, scale, and fade (Alpha)
An example of a basic sprite rendering solution using the 2D API.
To color individual sprites you have two options.
Option 1 ctx.filter
You could use the ctx.filter property and create a custom filter to color sprites.
However this is not optimal as filters are rather slow when compared to alternative solutions.
Option 2 Split Channels
To draw a sprite in any color you will need to split the image into its red green blue and alpha parts. Then you render each channel depending on the color you want it to display.
To avoid security limits you can split the image using the following functions
Copy image
Creates a copy of the image as a canvas
function copyImage(img) { // img can be any image type
const can = Object.assign(document.createElement("canvas"), {
width: img.width,
height: img.height,
});
can.ctx = can.getContext("2d");
can.ctx.drawImage(img, 0, 0, img.width, img.height);
return can;
}
Filter channel
This will copy an image and remove two or three RGBA color channels. It requires the function copyImage.
function imageFilterChannel(img, channel = "red") {
const imgf = copyImage(img);
// remove unwanted channel data
imgf.ctx.globalCompositeOperation = "multiply";
imgf.ctx.fillStyle = imageFilterChannel.filters[channel] ?? "#FFF";
imgf.ctx.fillRect(0,0, img.width, img.height);
// add alpha mask
imgf.ctx.globalCompositeOperation = "destination-in";
imgf.ctx.drawImage(img, 0, 0, img.width, img.height);
imgf.ctx.globalCompositeOperation = "source-over";
return imgf;
}
imageFilterChannel.filters = {
red: "#F00",
green: "#0F0",
blue: "#00F",
alpha: "#000",
};
Colored sprite
This function will load an image, create the 4 separate channel images and encapsulate it in a sprite object.
It requires the function imageFilterChannel.
NOTE there is no error checking
function createColorSprite(srcUrl) {
var W, H;
const img = new Image;
img.src = srcUrl;
const channels = [];
img.addEventListener("load",() => {
channels[0] = imageFilterChannel(img, "red");
channels[1] = imageFilterChannel(img, "green");
channels[2] = imageFilterChannel(img, "blue");
channels[3] = imageFilterChannel(img, "alpha");
API.width = W = img.width;
API.height = H = img.height;
API.ready = true;
}, {once: true});
const API = {
ready: false,
drawColored(ctx, x, y, scale, rot, color) { // color is CSS hex color #RRGGBBAA
// eg #FFFFFFFF
// get RGBA from color string
const r = parseInt(color[1] + color[2], 16);
const g = parseInt(color[3] + color[4], 16);
const b = parseInt(color[5] + color[6], 16);
const a = parseInt(color[7] + color[8], 16);
// Setup location and transformation
const ax = Math.cos(rot) * scale;
const ay = Math.sin(rot) * scale;
ctx.setTransform(ax, ay, -ay, ax, x, y);
const offX = -W / 2;
const offY = -H / 2;
// draw alpha first then RGB
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = a / 255;
ctx.drawImage(channels[3], offX, offY, W, H);
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = r / 255;
ctx.drawImage(channels[0], offX, offY, W, H);
ctx.globalAlpha = g / 255;
ctx.drawImage(channels[1], offX, offY, W, H);
ctx.globalAlpha = b / 255;
ctx.drawImage(channels[2], offX, offY, W, H);
ctx.globalCompositeOperation = "source-over";
}
};
return API;
}
Used as follows
// to load
const image = createColorSprite("https://i.stack.imgur.com/RXAVJ.png");
// to render with red
if (image.ready) { image.drawColored(ctx, 100, 100, 1, 0, "#FF0000FF") }
DEMO
Using the functions above the demo shows how to use them and will give you some feedback in regards to the performance. It uses some of the code from the sprite example linked above.
Demo renders 200 images.
Note that there is some room for performance gains.
Don`t render channel image if that channel is zero
Don's use CSS color string, use an array of unit colors, saves the need to parse CSS color string. eg RGBA red as [1,0,0,1]
Use images that closely match the size they will be displayed, The images in the demo are many times larger than they are displayed.
function copyImage(img) { // img can be any image type
const can = Object.assign(document.createElement("canvas"), {
width: img.width,
height: img.height,
});
can.ctx = can.getContext("2d");
can.ctx.drawImage(img, 0, 0, img.width, img.height);
return can;
}
function imageFilterChannel(img, channel = "red") {
const imgf = copyImage(img);
// remove unwanted channel data
imgf.ctx.globalCompositeOperation = "multiply";
imgf.ctx.fillStyle = imageFilterChannel.filters[channel] ?? "#FFF";
imgf.ctx.fillRect(0,0, img.width, img.height);
// add alpha mask
imgf.ctx.globalCompositeOperation = "destination-in";
imgf.ctx.drawImage(img, 0, 0, img.width, img.height);
imgf.ctx.globalCompositeOperation = "source-over";
return imgf;
}
imageFilterChannel.filters = {
red: "#F00",
green: "#0F0",
blue: "#00F",
alpha: "#000",
};
function createColorSprite(srcUrl) {
var W, H;
const img = new Image;
img.src = srcUrl;
const channels = [];
img.addEventListener("load",() => {
channels[0] = imageFilterChannel(img, "red");
channels[1] = imageFilterChannel(img, "green");
channels[2] = imageFilterChannel(img, "blue");
channels[3] = imageFilterChannel(img, "alpha");
API.width = W = img.width;
API.height = H = img.height;
API.ready = true;
}, {once: true});
const API = {
ready: false,
drawColored(ctx, x, y, scale, rot, color) { // color is CSS hex color #RRGGBBAA
// eg #FFFFFFFF
// get RGBA from color string
const r = parseInt(color[1] + color[2], 16);
const g = parseInt(color[3] + color[4], 16);
const b = parseInt(color[5] + color[6], 16);
const a = parseInt(color[7] + color[8], 16);
// Setup location and transformation
const ax = Math.cos(rot) * scale;
const ay = Math.sin(rot) * scale;
ctx.setTransform(ax, ay, -ay, ax, x, y);
const offX = -W / 2;
const offY = -H / 2;
// draw alpha first then RGB
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = a / 255;
ctx.drawImage(channels[3], offX, offY, W, H);
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = r / 255;
ctx.drawImage(channels[0], offX, offY, W, H);
ctx.globalAlpha = g / 255;
ctx.drawImage(channels[1], offX, offY, W, H);
ctx.globalAlpha = b / 255;
ctx.drawImage(channels[2], offX, offY, W, H);
ctx.globalCompositeOperation = "source-over";
}
};
return API;
}
var image = createColorSprite("https://i.stack.imgur.com/C7qq2.png?s=328&g=1");
var image1 = createColorSprite("https://i.stack.imgur.com/RXAVJ.png");
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
var w,h;
function resize(){ w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
resize();
addEventListener("resize",resize);
function rand(min,max){return Math.random() * (max ?(max-min) : min) + (max ? min : 0) }
const randHex = () => (Math.random() * 255 | 0).toString(16).padStart(2,"0");
const randColRGB = () => "#" + randHex() + randHex() + randHex() + "FF";
function DO(count,callback){ while (count--) { callback(count) } }
const sprites = [];
DO(200,(i)=>{
sprites.push({
x : rand(w), y : rand(h),
xr : 0, yr : 0, // actual position of sprite
r : rand(Math.PI * 2),
scale: rand(0.1,0.25),
dx: rand(-2,2), dy : rand(-2,2),
dr: rand(-0.2,0.2),
color: randColRGB(),
img: i%2 ? image : image1,
});
});
function update(){
var ihM,iwM;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
if(image.ready && image1.ready){
for(var i = 0; i < sprites.length; i ++){
var spr = sprites[i];
iw = spr.img.width;
ih = spr.img.height;
spr.x += spr.dx;
spr.y += spr.dy;
spr.r += spr.dr;
iwM = iw * spr.scale * 2 + w;
ihM = ih * spr.scale * 2 + h;
spr.xr = ((spr.x % iwM) + iwM) % iwM - iw * spr.scale;
spr.yr = ((spr.y % ihM) + ihM) % ihM - ih * spr.scale;
spr.img.drawColored(ctx, spr.xr, spr.yr, spr.scale, spr.r, spr.color);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
Old answer
Adding color FXs
After the image is drawn set either ctx.globalCompositeOperation = "color", or ctx.globalCompositeOperation = "hue" and draw over the image in the color you want.
Example
ctx.drawImage(img, 0, 0, 50, 50); // draw image. Ensure that the current
// composite op is
// "source-over" see last line
ctx.globalCompositeOperation = "color"; // set the comp op (can also use "hue")
ctx.fillStyle = "red"; // set fillstyle to color you want
ctx.fillRect(0,0,50,50); // draw red over image
ctx.globalCompositeOperation = "source-over";// restore default comp op
For more info see MDN Global Composite Operations
You can try positioning a layer over the image with a partially transparent background:
.container{
position:relative;
width:fit-content;
}
.layer{
position:absolute;
height:100%;
width:100%;
background-color:rgb(0, 98, 255, 0.5);
z-index:1;
}
<div class="container">
<div class="layer"></div>
<img src="https://i.stack.imgur.com/XFpL3.png">
</div>

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!

Re-fill area of erased area for canvas

I am trying to fill color in image using below code snippet for filling color on Image of canvas . Its successfully filling color in canvas. Now I am trying to erase filled color on touch of user using this code snippet for erasing color on Image of canvas . Its erasing color & setting transparent area on that touched position. Now I want to refill that area on user touch with colors but its not allowing me to color on that because of transparent pixels. So Is there any way to refill pixels with color Or Is there any other way to erase color from image of canvas ? Any reply will be appreciated.
code snippet for filling color on Image of 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";
function draw(color) {
ctx.drawImage(img, 0, 0);
}
canvas.onclick = function(e){
var rect = canvas.getBoundingClientRect();
var x = e.clientX-rect.left,
y = e.clientY-rect.top;
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(x-5,y-5,10,0,2*Math.PI);
ctx.fill();
}
code snippet for erasing color on Image of canvas
(function() {
// Creates a new canvas element and appends it as a child
// to the parent element, and returns the reference to
// the newly created canvas element
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = width || 100;
canvas.node.height = height || 100;
parent.appendChild(canvas.node);
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, width, height);
var ctx = canvas.context;
// define a custom fillCircle method
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
};
ctx.clearTo = function(fillColor) {
ctx.fillStyle = fillColor;
ctx.fillRect(0, 0, width, height);
};
ctx.clearTo(fillColor || "#ddd");
// bind mouse events
canvas.node.onmousemove = function(e) {
if (!canvas.isDrawing) {
return;
}
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
var radius = 10; // or whatever
var fillColor = '#ff0000';
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
canvas.node.onmousedown = function(e) {
canvas.isDrawing = true;
};
canvas.node.onmouseup = function(e) {
canvas.isDrawing = false;
};
}
var container = document.getElementById('canvas');
init(container, 531, 438, '#ddd');
})();
Warning untested code!
// create a clipping region using your erasing rect's x,y,width,height
context.save();
context.beginPath();
context.rect(erasingRectX,erasingRectY,erasingRectWidth,erasingRectHeight);
context.clip();
// redraw the original image.
// the image will be redrawn only into the erasing rects boundary
context.drawImage(yourImage,0,0);
// compositing: new pixels draw only where overlapping existing pixels
context.globalCompositeOperation='source-in';
// fill with your new color
// only the existing (clipped redrawn image) pixels will be colored
context.fillStyle='red';
context.fillRect(0,0,canvas.width,canvas.height);
// undo the clipping region
context.restore();

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);

How can I stop HTML5 Canvas Ghosting?

I made a small program that:
changes the mouse cursor inside the canvas to a black square
gives the black square a nice trail that fades away over time (the point of the program)
Here's the code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.save();
ctx.globalAlpha = 0.1; // the opacity (i.e. fade) being applied to the canvas on each function re-run
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
ctx.fillStyle = "black";
ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
Here's a live example: https://jsfiddle.net/L6j71crw/2/
Problem
However the trail does not fade away completely, and leaves a ghosting trail.
Q: How can I remove the ghosting trail?
I have tried using clearRect() in different ways, but it just clears the entire animation leaving nothing to display. At best it just removes the trail and only fades the square cursor alone, but it still doesn't make the cursor completely transparent when the fading process is completed. I have tried finding posts about it, but I found nothing that gave a definitive answer and—most importantly—no posts with a working example.
Any ideas?
Try having a list of positions, this won't leave a ghost trail!
my code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var Positions = [];
var maxlength = 20;
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
var V2 = function(x, y){this.x = x; this.y = y;};
function getMousePos(canvas, e) {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var e = 0; e != Positions.length; e++)
{
ctx.fillStyle = ctx.fillStyle = "rgba(0, 0, 0, " + 1 / e + ")";
ctx.fillRect(Positions[e].x, Positions[e].y, 8, 8);
}
if(Positions.length > 1)
Positions.pop()
//ctx.save();
//ctx.globalAlpha = 0.5; // the opacity (i.e. fade) being applied to the canvas on each function re-run
//ctx.fillStyle = "#fff";
//ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
//ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
Positions.unshift(new V2(pos.x, pos.y));
if(Positions.length > maxlength)
Positions.pop()
//ctx.fillStyle = "black";
//ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
JSFiddle: https://jsfiddle.net/L6j71crw/9/
Edit: made the cursor constant.

Categories

Resources