How to display another canvas without zoom in fabricjs - javascript

In short, I am developing a project in which there are two canvases: a canvas that supports zoom and drawing, a second canvas - a duplicate of the first canvas, which should display a general view without zoom. Unfortunately, I do not understand how to implement this, since the second canvas also displays a picture with a zoom. Please, help!
Here is a small example of what I have:
var c1 = document.getElementById("scale");
var c2 = document.getElementById("static");
var canvas = this.__canvas = new fabric.Canvas(c1, {
isDrawingMode: true
});
var ctx1 = c1.getContext("2d");
var ctx2 = c2.getContext("2d");
function copy() {
var imgData = ctx1.getImageData(0, 0, 512, 512);
ctx2.putImageData(imgData, 0, 0);
}
fabric.Image.fromURL(
"https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
img => {
img.scaleToWidth(canvas.width);
canvas.setBackgroundImage(img);
canvas.requestRenderAll();
},
{
crossOrigin: "Annoymous"
}
);
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var pointer = canvas.getPointer(opt.e);
var zoom = canvas.getZoom();
zoom = zoom + delta/200;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
setInterval(() => {
copy();
}, 10)
canvas {
border: 1px solid black;
}
#static {
position: relative;
top: -300px;
left: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js"> </script>
This is interactive canvas
<canvas id="scale" width="300" height="300"></canvas>
<canvas id="static"width="300" height="300"></canvas>

There is not a perfect solution because fabricJS does not support rendering on more targets.
There is a toCanvasElement method here that creates a copy to another canvas, but it does not let you specify the canvas you want to draw on.
So here what i m doing is, at some point:
reset the zoom to 1
render to a new canvas
put back the zoom to what it was
copy that canvas on the static canvas
There is an extra copy, that is fast on modern hardware, to the point that you do not care, but would be nicer to add a canvas argument to that toCanvasElement method in order to get the drawing happening on the specified canvas immediately
Then apart from that there is the quesiton on when firing that copy.
'after:render' is not a great idea, it will loop forever and it will also redraw on zoom changes, that is not what you want.
For now i use object:added, you can add also object:modified, and maybe something else but ideally you will call a copy manually when needed when you run some code that will change some of the drawing properties.
var c1 = document.getElementById("scale");
var c2 = document.getElementById("static");
var canvas = this.__canvas = new fabric.Canvas(c1, {
isDrawingMode: true
});
var ctx2 = c2.getContext("2d");
function copy(copiedCanvas) {
ctx2.drawImage(copiedCanvas,0,0);
}
fabric.Image.fromURL(
"https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
img => {
img.scaleToWidth(canvas.width);
canvas.setBackgroundImage(img);
canvas.requestRenderAll();
},
{
crossOrigin: "Annoymous"
}
);
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var pointer = canvas.getPointer(opt.e);
var zoom = canvas.getZoom();
zoom = zoom + delta/200;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
function afterRender() {
var originalVP = canvas.viewportTransform;
canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
copy(canvas.toCanvasElement());
canvas.viewportTransform = originalVP;
}
canvas.on('object:added', afterRender);
canvas.on('object:modified', afterRender);
canvas {
border: 1px solid black;
}
#static {
position: relative;
top: -300px;
left: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js"> </script>
This is interactive canvas
<canvas id="scale" width="300" height="300"></canvas>
<canvas id="static"width="300" height="300"></canvas>

Your issue is that you are getting the "ImageData" from the context
but you should be using the fabric.Canvas object...
The context only gives you what you see, so those two canvases will be a mirror image, you have to get the data from the fabric.Canvas that is the one that has the "big picture" with all the edits.
In my prototype I'm using canvas.toJSON to get the data then to draw that data I use loadFromJSON, but I did notice some flickering the new image is loaded so I decided to use an image instead of the static canvas, that way the transition is seamless
see my simple prototype below:
var c1 = document.getElementById("scale");
var static = document.getElementById("img");
var canvas = new fabric.Canvas(c1, {isDrawingMode: true});
function copy() {
var data = canvas.toObject();
var c = new fabric.Canvas();
c.loadFromJSON(data, function() {
static.src = c.toDataURL({ format: 'png' });
});
}
fabric.Image.fromURL(
"https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
img => {
img.scaleToWidth(canvas.width);
canvas.setBackgroundImage(img);
canvas.requestRenderAll();
}, { crossOrigin: "anonymous" }
);
canvas.on('mouse:wheel', function(opt) {
var pointer = canvas.getPointer(opt.e);
var zoom = canvas.getZoom() + opt.e.deltaY / 200;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
canvas.zoomToPoint({
x: opt.e.offsetX,
y: opt.e.offsetY
}, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
setInterval(copy, 200)
canvas {
border: 2px solid black;
}
img {
border: 2px solid red;
position: absolute;
top: 5px; right: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js">
</script>
<canvas id="scale" width="300" height="150"></canvas>
<br><img id="img" width="300" height="150">
Unless we get some browser compatibility issue (I only tested chrome), the result should look like:

Related

How to pan in PaperJS without using PaperScript

Referring to
How to pan using paperjs
I was trying to implement the pan.
This however only works for me when I use PaperScript and stops to work when I do it in regular JavaScript.
var canvas = document.querySelector('#myCanvas');
paper.install(window);
window.onload = function() {
paper.setup(canvas);
var myCircle = new Path.Circle(new Point(100, 70), 50);
myCircle.fillColor = 'black';
var toolPan = new paper.Tool();
toolPan.onMouseDrag = function (event) {
var offset = event.downPoint - event.point;
// console.log(offset);
paper.view.center = paper.view.center + offset;
};
view.draw();
}
html,
body {
margin: 0;
overflow: hidden;
height: 100%;
}
canvas[resize] {
width: 100%;
height: 100%;
}
<canvas id="myCanvas" resize></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.11/paper-full.min.js"></script>
Logging out offset to the console tells me it can’t substract the points. In PaperScript the exact same works well, however I have to make it work in regular JavaScript for my Project.
In PaperScript, you can use mathematical operators on Point instances but you can't do this in JavaScript so you have to use the corresponding methods instead.
point1 + point2 => point1.add(point2), etc...
Here is the corrected code:
var canvas = document.querySelector('#myCanvas');
paper.install(window);
window.onload = function() {
paper.setup(canvas);
var myCircle = new Path.Circle(new Point(100, 70), 50);
myCircle.fillColor = 'black';
var toolPan = new paper.Tool();
toolPan.onMouseDrag = function (event) {
var offset = event.downPoint.subtract(event.point);
// console.log(offset);
paper.view.center = paper.view.center.add(offset);
};
view.draw();
}
html,
body {
margin: 0;
overflow: hidden;
height: 100%;
}
canvas[resize] {
width: 100%;
height: 100%;
}
<canvas id="myCanvas" resize></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.11/paper-full.min.js"></script>

Fabric.js remnants of old canvas remain

I want to display the content of a canvas as the background of another canvas and draw a bunch of rectangles on there. I need to dynamically change:
the canvas from which to load the background image for my finalcanvas
which rectangles to draw
It's easiest if I start this process from scratch. I have imitated starting from scratch with a simple button. However, after redrawing my canvas, previous information from fabric.js remains present after dragging an item of a canvas a bit. This means that the old canvas was not cleared properly. I tried playing around with .clear() and .depose(), but to no avail.
In case the description is vague, here an image:
And an small reproducible example:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello</title>
</head>
<body>
<canvas id="finalcanvas"></canvas>
<canvas id="backgroundcanvas" width="200" height="100"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js" type="text/javascript"></script>
<script>
function load_plane_onto_active_canvas() {
var c = document.getElementById('backgroundcanvas');
var ctx = c.getContext("2d");
var bg = c.toDataURL("image/png");
var canvas = new fabric.Canvas('finalcanvas', {
width: 333,
height: 333
});
canvas.setBackgroundImage(bg, canvas.renderAll.bind(canvas));
canvas.on("mouse:over", function(e) {
console.log(e.target)
});
// add 100 rectangles
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
rect = new fabric.Rect({
width: 10,
height: 10,
left: j * 15,
top: i * 15,
fill: 'green',
})
canvas.add(rect);
}
}
}
window.onload = function() {
// fill the background canvas with red
(function() {
var canvas = document.getElementById("backgroundcanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
}())
load_plane_onto_active_canvas()
}
</script>
<button onclick="load_plane_onto_active_canvas()">click me</button>
</body>
</html>
I hope someone can help me!
Notice that you're creating a new instance of fabric.Canvas in your load_plane_onto_active_canvas() function. So when you're clicking the button, the existing canvases stay intact, and you're actually calling clear() on your freshly created canvas. The DOM becomes messed up at this point, with several nested canvases inside of each other - you can take a peek at the DOM inspector to see that.
What you can do instead is create the canvas instance just once, then work with a reference to it later in your other functions.
const finalCanvas = new fabric.Canvas("finalcanvas", {
width: 333,
height: 333
});
// finalCanvas now exists in the global (window) scope
function load_plane_onto_active_canvas() {
var c = document.getElementById("backgroundcanvas");
var ctx = c.getContext("2d");
var bg = c.toDataURL("image/png");
finalCanvas.clear();
finalCanvas.setBackgroundImage(bg, finalCanvas.renderAll.bind(finalCanvas));
finalCanvas.on("mouse:over", function (e) {
// console.log(e.target);
});
// add 100 rectangles
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
rect = new fabric.Rect({
width: 10,
height: 10,
left: j * 15,
top: i * 15,
fill: "green"
});
finalCanvas.add(rect);
}
}
}
window.onload = function () {
// fill the background canvas with red
(function () {
var canvas = document.getElementById("backgroundcanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
})();
load_plane_onto_active_canvas();
};
document.querySelector("#b1").onclick = () => load_plane_onto_active_canvas();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.js"></script>
<canvas id="finalcanvas"></canvas>
<canvas id="backgroundcanvas" width="200" height="100"></canvas>
<button id="b1">start from scratch</button>

I would want to rescale my image in all directions in my canvas, but it does only towards bottom-right corner

Context
I have a canvas in which I draw an image. I would want to animate it: the image should be zoomed-in (rescaled) in all directions (vertical and horizontal expansions, towards the top, right, bottom, and left sides, all together and at the same time).
Expected results
At each iteration, the image goes x pixels towards the top and the bottom, the left, and the right sides of the canvas: it's a zoom. The pixels of the image that go beyond the canvas become not visible.
Actual results
At each iteration, the image goes x pixels towards the bottom and right sides of the canvas: it's a zoom. The pixels of the image that go beyond the canvas become not visible.
The problem
The image goes x pixels towards the bottom and right sides of the canvas instead of going x pixels towards the top and the bottom, the left, and the right sides of the canvas.
The question
How to make the image go x pixels towards the top and the bottom, the left, and the right sides of the canvas?
Minimal and Testable Example
How to test my example?
Download an image file titled "my_img.jpg", store it in a directory titled "my_dir". You can change the names; think to change them in the below source code too (line to be changed: var images = [ ....).
In this same directory, store my code in a file titled "index.HTML"
Open your browser to read "index.HTML"
Click on the "Start" button, the animation will begin and you will see my problem.
Sources
Comments
Note the use of ctx.drawImage(tmp_canvas, -(tmp_canvas.width - canvas.width)/2, -(tmp_canvas.height - canvas.height)/2);, which is censed to draw the zoomed image (from a new canvas named "tmp_canvas"), in the real canvas (whose context is ctx). I divide by 2 the difference between the zoomed image minus the real canvas, which normally corresponds to the fact to draw the zoomed image expanded towards left AND right. The same techniqye is used for top and bottom sides.
Clues
console.log(-(tmp_canvas.width - canvas.width)/2); shows 0, it should not. tmp_canvas.width should be the zoomed image's width. canvas.width should be the canvas' width.
index.HTML
<html>
<head>
<title>Creating Final Video</title>
</head>
<body>
<button id="start">Start</button>
<canvas id="canvas" width="3200" height="1608"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
var images = ["my_dir/my_img.jpg"];
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
function showSomeMedia() {
setImageListener();
setImageSrc();
}
function setImageSrc() {
img.src = images[0];
img.crossOrigin = "anonymous";
}
function imgOnLoad() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.restore();
var tmp_canvas = canvas.cloneNode();
var tmp_ctx = tmp_canvas.getContext("2d");
function zoomInCanvas() {
tmp_ctx.scale(1.001, 1.001);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(
tmp_canvas,
-(tmp_canvas.width - canvas.width) / 2,
-(tmp_canvas.height - canvas.height) / 2
);
}
var i = 0;
const interval = setInterval(function() {
if (i == 100) {
clearInterval(interval);
}
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
tmp_ctx.drawImage(canvas, 0, 0);
requestAnimationFrame(zoomInCanvas);
i++;
}, 1000);
}
function setImageListener() {
img.onload = function() {
imgOnLoad();
};
}
$("#start").click(function() {
showSomeMedia();
});
</script>
</body>
</html>
I don't know about how to solve some of the other problems with your code, however I can help with being able to rescale images in all directions.
$(document).ready(function(){
$('#resizable').resizable({
handles: 'n, e, s, w, ne, se, sw, nw'
});
});
#resizable {
width: 50%;
border: 1px solid black;
}
#resizable img {
width: 100%;
}
.ui-resizable-handle {
background: red;
border: 1px solid red;
width: 9px;
height: 9px;
z-index: 2;
}
.ui-resizable-se {
right: -5px;
bottom: -5px;
}
#resizable {
width: 150px;
height: 150px;
padding: 0.5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<img id="resizable" src="https://www.w3schools.com/w3css/img_lights.jpg">
Some of this code is from a question that I recently asked, and got an answer for.
In fact, the translate call doesn't change the tmp_canvas's image's dimensions. So I have to compute these new width and height and store them in parralel. The following code works perfectly:
<html>
<head>
<title>Creating Final Video</title>
</head>
<body>
<button id="start">Start</button>
<canvas id="canvas" width=3200 height=1608></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
var images = [
'my_dir/my_img.jpg'
];
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
function showSomeMedia() {
setImageListener();
setImageSrc();
}
function setImageSrc() {
img.src = images[0];
img.crossOrigin = "anonymous";
}
function imgOnLoad() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.restore();
var tmp_canvas = canvas.cloneNode();
var tmp_ctx = tmp_canvas.getContext("2d");
var img_new_width = tmp_canvas.width;
var img_new_height = tmp_canvas.height;
function zoomInCanvas() {
img_new_width *= 1.001
img_new_height *= 1.001
tmp_ctx.scale(1.001, 1.001);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(tmp_canvas, -(img_new_width - canvas.width)/2, -(img_new_height - canvas.height)/2);
}
var i = 0;
const interval = setInterval(function() {
if(i == 100) {
clearInterval(interval);
}
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
tmp_ctx.drawImage(canvas, 0, 0);
requestAnimationFrame(zoomInCanvas);
i++;
}, 1000);
}
function setImageListener() {
img.onload = function () {
imgOnLoad();
};
}
$('#start').click(function() {
showSomeMedia();
});
</script>
</body>
</html>

Draw rectangle over uploaded image

Once I upload an image I make 2 clicks over it. That's how (x1,y1) and (x2,y2) are obtained. Given these 4 numbers I waanna draw a rectangle over the image by means for example of P5.js. Then I should say rect(x1,y1,x2,y2) but this never happens. How can I deal with this problem (maybe not by P5)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Test</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<body>
<div id="app">
<button v-on:click="isHidden = false">Load img</button>
<img onclick="showCoords(event)" v-if="!isHidden" v-bind:src="src[z]"></img>
</div>
<script>
var test = new Vue({
el: '#app',
data: {
src: ["cat.jpg", "dog.jpg"],
z: Math.round(Math.random()),
isHidden: true,
}
})
var k=0;
var koors = [];
var flag = false;
function showCoords(event) {
if (k===0){
koors[0] = event.clientX;
koors[1] = event.clientY;
k+=1;
} else if (k===1){
flag = true;
koors[2] = event.clientX;
koors[3] = event.clientY;
}
if ((koors[3] != 0) && (flag)){
console.log(koors)
}
}
//p5
function setup(){
}
function draw(){
console.log(koors[0],koors[1],koors[2],koors[3]);
rect(koors[0],koors[1],koors[2],koors[3])
}
</script>
<script src="p5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<script src="js.js"></script>
</body>
</html>
P5 needs a canvas for rendering. If you don't initialize one, it creates it himself (and you are in trouble).
Also, P5 is powerful library, it has tools for events, image processing etc. For current example I wouldn't use any other library (vue).
I created canvas on top of the image (css) and rest is playing with P5.
var test = new Vue({
el: '#app',
data: {
src: [ "https://i.stack.imgur.com/XZ4V5.jpg", "https://i.stack.imgur.com/7bI1Y.jpg"],
z: Math.round(Math.random()),
isHidden: false,
}
})
var recStart;
var coords = {};
/*******************************************************************
* P5 setup
* run once, use for initialisation.
*
* Create a canvas, change dimensions to
* something meaningful(like image dim)
********************************************************************/
function setup(){
var canvas = createCanvas(480, 320);
canvas.parent('app');
}
/*******************************************************************
* P5 draw
* endless loop.
*
* Lets redraw rectangle until second click.
********************************************************************/
function draw(){
if(recStart)
drawRect();
}
/*******************************************************************
* drawRect
*
* Draw a rectangle. mouseX and mouseY are P5 variables
* holding mouse current position.
********************************************************************/
function drawRect(){
clear();
noFill();
stroke('red');
rect(coords.x, coords.y, mouseX-coords.x, mouseY-coords.y);
}
/*******************************************************************
* P5 mouseClicked
*
* P5 event. Again, mouseX and mouseY are used.
********************************************************************/
mouseClicked = function() {
if (mouseButton === LEFT) {
if(!recStart){ // start rectangle, give initial coords
coords.x = mouseX;
coords.y = mouseY;
recStart = true; // draw() starts to draw
} else {
recStart = false; // stop draw()
drawRect(); // draw final rectangle
coords = {}; // clear coords
}
}
};
canvas {
width: 500px;
height:500px;
z-index: 2;
border:1px solid;
position:absolute;
left: 0;
top: 0;
}
img {
z-index: 1;
position: absolute;
border: 2px solid black;
left: 0;
top: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<img id="pilt" v-if="!isHidden" v-bind:src="src[z]"></img>
</div>
It seems that you don't call draw anywhere. You may also consider draw the image into a canvas and than draw rect in this canvas. Checking if koors[3] != 0 is really risky as user may click at 0th pixel.

Optimise HTML5 canvas

Why does html5 canvas become very slow when I draw 2000 images and more ?
How can I optimise it?
Here's a demo that I've made, disable the "safe mode" by left clicking on the canvas and start moving your mouse until you get ~2000 images drawn
var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";
img.onload = Draw;
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];
$("canvas").mousemove(add)
function add(event) {
draw.push({x: event.clientX, y: event.clientY})
}
canvas.width = cnv.width();
canvas.height = cnv.height();
var safe = true;
cnv.contextmenu(function(e) { e.preventDefault() })
cnv.mousedown(function(event) {
if(event.which == 1) safe = !safe;
if(event.which == 3) draw = []
});
function Draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(Draw);
for(var i of draw) {
ctx.drawImage(img, i.x, i.y)
}
if(safe && draw.length > 300) draw = []
ctx.fillText("Images count: "+ draw.length,10, 50);
ctx.fillText("Left click to toggle the 300 images limit",10, 70);
ctx.fillText("Right click to clear canvas",10, 90);
}
Draw();
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 99999999999;
cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>
Codepen: http://codepen.io/anon/pen/PpeNme
The simple way with the actual code :
Don't redraw all your images at this rate.
Since in your example, the images are static, you actually don't need to redraw everything every frame : Just draw the latest ones.
Also, if you've got other drawings occurring (e.g your texts), you may want to use an offscreen canvas for only the images, that you'll redraw on the onscreen canvas + other drawings.
var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";
img.onload = Draw;
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];
$("canvas").mousemove(add)
function add(event) {
draw.push({
x: event.clientX,
y: event.clientY
})
}
canvas.width = cnv.width();
canvas.height = cnv.height();
// create an offscreen clone of our canvas for the images
var imgCan = canvas.cloneNode();
var imgCtx = imgCan.getContext('2d');
var drawn = 0; // a counter to know how much image we've to draw
var safe = true;
cnv.contextmenu(function(e) {
e.preventDefault()
})
cnv.mousedown(function(event) {
if (event.which == 1) safe = !safe;
if (event.which == 3) draw = []
});
function Draw() {
// clear the visible canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(Draw);
if (draw.length) { // onmy if we've got some objects to draw
for (drawn; drawn < draw.length; drawn++) { // only the latest ones
let i = draw[drawn];
// draw it on the offscreen canvas
imgCtx.drawImage(img, i.x, i.y)
}
}
// should not be needed anymore but...
if (safe && draw.length > 300) {
draw = [];
drawn = 0; // reset our counter
// clear the offscren canvas
imgCtx.clearRect(0, 0, canvas.width, canvas.height);
}
// draw the offscreen canvas on the visible one
ctx.drawImage(imgCan, 0, 0);
// do the other drawings
ctx.fillText("Images count: " + draw.length, 10, 50);
ctx.fillText("Left click to toggle the 300 images limit", 10, 70);
ctx.fillText("Right click to clear canvas", 10, 90);
}
Draw();
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 99999999999;
cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>
Now, if you need these images to be dynamic (i.e move at every frame), you may consider using imageDatas.
You can see this original post by user #Loktar, which explains how to parse your drawn image imageData, and then redraw it pixel per pixel on the visible canvas' imageData. You can also see this follow-up Q/A, which provides a color implementation of Loktar's idea.
On small images, this actually improves drastically the performances, but it has the huge inconvenient to not support alpha channel multiplication. You will only have fully transparent and fully opaque pixels. An other cons is that it may be harder to implement, but this is just your problem ;-)

Categories