Keep background image when using Eraser - javascript

I need to build a drawing board with erase function using PaperJS. The drawing board need to have a background image and people can draw on the canvas or erase existing path.
//erase function
function onMouseDown(event) {
path = new paper.Path();
path.strokeColor = 'red';
path.strokeWidth = 10;
path.strokeCap = 'round';
path.blendMode = 'destination-out';
}
function onMouseDrag(event) {
path.add(event.point);
}
//setup background image
var bgimage = "./cat.jpg";
var raster = new paper.Raster({
source: bgimage,
position: paper.view.center
});
The problem of using this method is that the background image will also be erased.
Erase Result: http://i.stack.imgur.com/0D4TU.png
JSFiddle: https://jsfiddle.net/9kaffsg4/2/
Is there any way to erase the existing path, without erase the background image?

If your background image does not change: you can put it in an <img> bellow your <canvas>?
If you want to be able to edit your image: you can redraw the image, then your drawing (treated separately), every frame.

I have solved the problem by using two canvas (one is for basic drawing function, the other is the background canvas). And then overlap the two canvas.

I expanded on your initial code example so that I could demonstrate using layers as a solution. Basically, three layers are created. They are created in the order you see them in (1:background, 2:foreground, 3:clearBtn). The layers created earlier are visualy "underneath" the layers created later.
In this way, you can keep your static raster graphic on the bottom-most layer (background), create your "eraser" paths on the middle layer (foreground), and you can remove/clear these "eraser" paths by clicking on the "clear" button. The clear button is on the top-most layer so that it doesn't become obscured by the "eraser paths". I just added the clear button to emphasize the added organizational control that layers provide.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.12/paper.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<style type="text/css">
canvas {
border: 1px solid #000;
}
</style>
<script type="text/paperscript" canvas="myCanvas">
var layers = {
"background": new Layer(),
"foreground": new Layer(),
"clearBtn": new Layer()
};
var paths = [];
//erase function
function onMouseDown(event) {
layers.foreground.activate();
path = new paper.Path();
path.strokeColor = 'red';
path.strokeWidth = 10;
path.strokeCap = 'round';
path.blendMode = 'destination-out';
paths.push(path);
}
function onMouseDrag(event) {
layers.foreground.activate();
path.add(event.point);
}
//setup background image
layers.background.activate();
var bgimage = "http://placehold.it/300x150";
var raster = new paper.Raster({
source: bgimage,
position: paper.view.center
});
// Build a "clear button"
// Clicking this will remove all children
// from the foreground layer
layers.clearBtn.activate();
var clearBtn = new Path.Rectangle({
point: new Point(250, 2),
size: new Size(50, 20),
style: {
fillColor: 'white',
strokeColor: 'black'
}
});
var txt = new PointText({
point: clearBtn.position + new Point(0,4),
content: 'Clear',
justification: 'center'
});
var clearBtnGrp = new Group({
children: [clearBtn, txt]
});
clearBtnGrp.on('mousedown', function () {
for ( var i = 0; i < paths.length; i++ ) {
paths[i].remove();
}
});
</script>
</head>
<body>
<canvas id="myCanvas"></canvas>
</body>
</html>
Edit I updated this to cache paths in an array because the previous example did not always clear all layer children.

Related

Issues with Prototype Interactive Space Map [duplicate]

This is my easel js function, it draws a red circle and an image, however the circle is showing but the image isn't.
function Start() {
var stage = new createjs.Stage("DogeCanvas");
var dog = new createjs.Bitmap("doge.jpg");
var circle = new createjs.Shape();
circle.graphics.beginFill("red").drawCircle(0, 0, 50);
circle.x = 100;
circle.y = 100;
dog.x=100;
dog.y=100;
stage.addChild(circle, dog);
stage.update();
}
And this is the html file
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script src="http://code.createjs.com/easeljs-0.7.0.min.js"></script>
<script src="Doge.js"></script>
</head>
<body bgcolor="#C7C7C7" onload="Start();">
<canvas id="DogeCanvas" width="480" height="320"></canvas>
</body>
</html>
Why? help please, seems like in everyones else code this works but why this isn't working for me?
The image is likely not loaded yet.
You can add a Ticker to the stage to constantly update it (which most applications do, since there is other things changing over time)
Example:
createjs.Ticker.on("tick", stage);
// OR
createjs.Ticker.addEventListener("tick", stage);
// OR
createjs.Ticker.on("tick", tick);
function tick(event) {
// Other stuff
stage.update(event);
}
Listen for the onload of the image, and update the stage again
Example:
var bmp = new createjs.Bitmap("path/to/image.jpg");
bmp.image.onload = function() {
stage.update();
}
Preload the image with something like PreloadJS before you draw it to the stage. This is a better solution for a larger app with more assets.
Example:
var queue = new createjs.LoadQueue();
queue.on("complete", function(event) {
var image = queue.getResult("image");
var bmp = new createjs.Bitmap(image);
// Do stuff with bitmap
});
queue.loadFile({src:"path/to/img.jpg", id:"image"});

How to render HTML Canvas without blur but while maintaining canvas size and object positions?

I'm looking for a way to render an existing 2D HTML Canvas in such a way that its contents are not blurry. However, as an additional constraint, I want to maintain the physical size of the canvas on the screen and (preferably) also maintain the draw coordinates on the screen. The canvas is a fixed size.
For example, if I have a 400x400 canvas with a circle with radius 4 drawn at the center, I want to upscale the drawn components without the positions shifting (IE the circle will still render in the same place it would have before, but won't be as blurry).
I am currently defining the dimensions of the canvas in the HTML using width and height (not CSS), which is something I would also like to keep. Setting the CSS to the HTML value via JS seems to not make a difference.
Note that I am not simply drawing lines - I have text (which looks horrible), as well as moving shapes. I also have no actual images - I simply want the canvas elements to look like they were proper vector graphics instead of the blurry mess they currently are.
Things I have tried (and why they don't work for me):
- LittleJoe's answer on Canvas drawings, like lines, are blurry - Final canvas size is changed, cutting off 3/4 of the canvas. Note that none of the answers on this SO post actually solve my problem.
- context.translate(0.5, 0.5); - Does not solve problem at all as I am not trying to draw only lines
I am currently using Safari as my browser. However, the problem exists across browsers.
Reprex Below (only shows blurry text, which is the most obviously blurry component since it is directly adjacent to regular non-blurry non-Canvas text). Please excuse the act of shoving the JS into the HTML file - this is only for demonstration.
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>Example - Graphics</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<script>
function createNew(canvasid, title) {
let newgame = new NewGame(canvasid, title);
newgame.startGame(canvasid);
}
function NewGame(canvasid, title) {
this.text = []; //array containing all text objects
this.canvas = document.getElementById(canvasid);
this.context = this.canvas.getContext("2d");
this.startGame = function (canvasid) {
this.setupInterval(20);
CreateText(4, 12, "#FFFFFF", 12, "Arial", "left", "Text (Blurry)", this);
CreateText(4, 24, "#FFFFFF", 12, "Arial", "left", title, this);
};
this.setupInterval = function(updatefreq) {
setInterval(this.update_main, updatefreq, this);
};
this.clearCanvas = function () {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
this.update_main = function (currgame) {
currgame.clearCanvas();
currgame.draw_main(canvasid);
};
//Main draw loop. Handles render order.
this.draw_main = function (canvasid) {
let i;
for (i = 0; i < this.text.length; i += 1) {
this.text[i].draw();
}
};
}
function DMKText(x, y, fillStyle, fontsize, font, textAlign, content, currgame) {
this.x = x;
this.y = y;
this.fillStyle = fillStyle;
this.fontsize = fontsize;
this.font = font;
this.textAlign = textAlign;
this.content = content;
this.createtime = currgame.frameNo;
this.existtime = 0;
this.update = function () {
};
this.draw = function () {
let ctx = currgame.context; //game window
ctx.fillStyle = this.fillStyle;
ctx.font = "" + this.fontsize + "px " + this.font;
ctx.textAlign = this.textAlign;
ctx.fillText(this.content, this.x, this.y);
};
return this;
}
function CreateText(x, y, fillStyle, fontsize, font, textAlign, content, currgame) {
let newtext = new DMKText(x, y, fillStyle, fontsize, font, textAlign, content, currgame);
currgame.text.push(newtext);
return newtext;
}
</script>
<body>
<style>
canvas { background-color: black; }
</style>
<div id="canvasdiv">
<canvas id="canvas_1" width="320" height="160" style="border:1px solid #DDDDDD;"></canvas>
</div>
<br>
<button type="button" onclick="createNew('canvas_1', 'Example')">Run Simulation</button>
</body>
</html>
Full source code for the project I'm working on located at GitHub. Bottom of README has links to live examples.

Create non-animated spritesheet in JS

I'm struggling to get an image of a deck of small cards into spritesheet form using javascript. I need to get them into an array so as to shuffle them. Here's what I've got so far. Only the canvas shows up light blue against a dark blue background.
<html>
<head>
<style>
canvas#game-canvas {
position: absolute;
top: 5%;
left: 5%;
background: lightblue;
height: 281px;
width: 500px;
}
body {
background: darkblue;
}
</style>
<script type="text/javascript" src="jquery1_10_2_min.js"></script>
<script>
var canvas = document.getElementById("game-canvas");
var ctx = canvas.getContext("2d");
function SpriteSheet(path, frameWidth, frameHeight) {
this.image = new Image();
this.frameHeight = frameHeight;
this.frameWidth = frameWidth;
// calculate the number of frames in a row after the image loads
var self = this;
this.image.onload = function() {
self.framesPerRow = Math.floor(this.image.width / frameWidth);
};
this.image.src = path;
}
var spritesheet = new SpriteSheet('small_playing_cards.png', 54, 65);
</script>
</head>
<body>
<canvas id="game-canvas"></canvas>
</body>
</html>
That's an duplicated answer, anyway you can do it by creating one new canvas and drawing it on the main.
PS: that's not my costume code, so I'll do somethings I'd not do, as create variables, etc.
1. Create canvas element asigned as value from one variable;
var Area=document.createElement("canvas")
2. Set the frame size to this canvas;
Area.width=FrameWidth,
Area.height=FrameHeight
3. Draw the spritesheet in this canvas in the x/y (negative) where the frame you want is located;
var AContext=Area.getContext("2d");
AContext.drawImage(SpriteSheet,0-FrameX,0-FrameY);
4. Draw this canvas as image in the main canvas, but with x/y preference;
MainCanvas.drawImage(Area,PreferenceX,PreferenceY)
It's like these steps you can draw one frame from an spritesheet image. I'm not sure if it's that you want, I can't understand your question as well.

Save canvas and use it as background img

Any-Kind-Soul-Out-there,
I spending hours figuring out how could i done the coding in such a way that when after user drawn on the canvas, they can click a btn called "save". After which the canvas's image will appear as a BG img of the webpage.
Even after user close the web browser, the bg img is still there when user open it again. I'm not sure is it possible to do it or not.
Need some help here, if need my full coding i can provide.
Below is my current coding when user clicked "save" btn. (Open new window as an image.)
// Save image
var saveImage = document.createElement("button");
saveImage.innerHTML = "Save canvas";
saveImage.addEventListener("click", function (evt) {
window.open(canvas.toDataURL("image/png"));
evt.preventDefault();
}, false);
document.getElementById("main-content").appendChild(saveImage);
You might use toDataURL to set the background, and localStorage to store 'permanently' the image :
http://jsbin.com/japekuzi/1/
use draw to fill the canvas with random rects, and set button to set and store current canvas as background.
Notice that when you (re)run the jsbin, it stills keeps its latest background.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<canvas id='cv'></canvas>
<button id='dr'>draw</button>
<button id='st'>set</button>
</body>
</html>
JS :
var $=document.getElementById.bind(document);
var cv = $('cv');
var ctx = cv.getContext('2d');
function randFill() {
ctx.clearRect(0,0,cv.width, cv.height);
for(var i=0; i<10; i++) {
var c= 0 | ( Math.random()* 360);
ctx.fillStyle = 'hsl('+c+',75%,75%)';
ctx.fillRect(Math.random()*cv.width*0.8,
Math.random()*cv.height*0.8, 20, 20);
}
}
function set() {
localStorage.setItem("bg", cv.toDataURL());
retrieve();
}
function retrieve() {
var cvURL = localStorage.getItem("bg", cv.toDataURL());
if (!cvURL) return;
document.body.style. backgroundImage ='url('+ cvURL+')';
}
randFill();
retrieve();
$('dr').onclick=randFill;
$('st').onclick=set;

Diagram creater using KineticJS

I'm working on a simple diagram editor using KineticJS. I would like to use two separate canvases for the palette area (that contains a number of Kinetic.Groups that represent the different nodes of the network I might create), and the diagramming area where I can add nodes from the palette via drag and drop, and then add connections between the various nodes as specific anchor points. I'm having trouble figuring out the drag and drop process from the palette canvas of (stationary) Kinetic.Groups over to the other canvas containing diagramming area. I'm guessing that I need to fire off of the dragstart event for the palette objects (although I don't want these themselves to be draggable), and then do something like create an opague copy of the palette object that can be dragged around, and finally dropped into the diagramming area (with full opacity).
Can groups be dragged outside of a the staging canvases boundaries? Maybe I need to generate an image when I start to drag from the palette, drag that image over, and then create another group when dropping into the diagramming area.
Does any one know of any examples that might point me in the right direction, or who can offer some insight (even code) into the required process. I've searched the KineticJS examples but can't quite find enough to get me going.
Here’s one way to drag nodes from a source palette into a destination group:
A Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
Network nodes are represented by small icons (which are really small kinetic image objects).
The user can drag any icon from the source palette to any destination group.
The groups are just defined areas on the canvas, but could be Kinetc.Groups for more flexibility.
During the dragend event, a new duplicate copy of the dragged icon is created in the destination group.
After the dragend event is complete, the original palette icon is automatically moved from
The newly created duplicate icon can be dragged around the destination group (but not outside that group).
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.5.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
// image loader
var imageURLs=[];
var imagesOK=0;
var imgs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempPC.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempServer.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempRouter.png");
loadAllImages();
function loadAllImages(callback){
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK==imageURLs.length ) {
start();
}
};
img.src = imageURLs[i];
}
}
// top icon positions
var nextIconX=20;
var nextIconY=20;
// define groups
var groups=[];
groups.push({x:0,y:100,w:175,h:250,fill:"skyblue"});
groups.push({x:175,y:100,w:175,h:250,fill:"cornsilk"});
// add boundary info to each group
// draw colored rect to show group area
for(var i=0;i<groups.length;i++){
var g=groups[i];
g.left=g.x;
g.right=g.x+g.w;
g.top=g.y;
g.bottom=g.y+g.h;
var rect=new Kinetic.Rect({
x:g.x,
y:g.y,
width:g.w,
height:g.h,
fill:g.fill,
stroke:"gray"
});
layer.add(rect);
}
// hittest for each group
function groupHit(x,y){
for(var i=0;i<groups.length;i++){
var g=groups[i];
if(x>g.left && x<g.right && y>g.top && y<g.bottom){return(i);}
}
return(-1);
}
function start(){
makePaletteIcon(imgs[0]);
makePaletteIcon(imgs[1]);
makePaletteIcon(imgs[2]);
layer.draw();
}
function makePaletteIcon(img){
// make an icon that stays in the pallette tray
var fixedIcon=newImage(nextIconX,nextIconY,img,false);
layer.add(fixedIcon);
// make an icon that is dragged from the tray to a group
var dragIcon=makeDraggableIcon(nextIconX,nextIconY,img);
layer.add(dragIcon);
// calc the next icon position
nextIconX+=(img.width+20);
}
function makeDraggableIcon(x,y,img){
var i=newImage(x,y,img,true);
//
i.trayX=x;
i.trayY=y;
//
i.setOpacity(0.50);
i.on("dragend",function(){
var x=this.getX();
var y=this.getY();
// if this pallette icon was not dropped in a group
// put the icon back in the tray and return
var hit=groupHit(x,y);
if(hit==-1){
this.setPosition(this.trayX,this.trayY);
return;
}
// add a copy of this icon to the drop group
var component=newImage(x,y,this.getImage(),true);
// set drag limits
var group=groups[hit];
component.maxDragLeft=group.left;
component.maxDragRight=group.right;
component.maxDragTop=group.top;
component.maxDragBottom=group.bottom;
// limit component dragging to inside the assigned group
component.setDragBoundFunc(function(pos) {
var xx=pos.x;
var yy=pos.y;
var w=this.getWidth();
var h=this.getHeight();
if(pos.x<this.maxDragLeft){xx=this.maxDragLeft;}
if(pos.x+w>this.maxDragRight){xx=this.maxDragRight-w;}
if(pos.y<this.maxDragTop){yy=this.maxDragTop;}
if(pos.y+h>this.maxDragBottom){yy=this.maxDragBottom-h;}
return{ x:xx, y:yy };
});
layer.add(component);
// move the dragIcon back into the pallette tray
this.setPosition(this.trayX,this.trayY);
layer.draw();
});
return(i);
}
// make a new Kinetic.Image
function newImage(x,y,img,isDraggable){
var i=new Kinetic.Image({
image:img,
x: x,
y: y,
width: img.width,
height: img.height,
draggable:isDraggable
});
return(i);
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Drag any icon from top into blue or yellow group</p>
<div id="container"></div>
</body>
</html>

Categories