canvas animation unintentionally drawn using multiple imgdata - javascript

I have a div that contains an img tag and a canvas. When I drag an image into the div, the img.src changes, the new picture appears, and in the canvas, a pointillization animation using the data from the img tag is drawn.
When I drag and drop imgA into the div, imgA appears in the img tag, and the canvas does the animation using imgA, which is what I want.
The problem is here:
From this point, if I drag and drop imgB into the div, imgB appears in the img tag, BUT, the canvas animation uses imgA + imgB data.
I think what I did is this:
I drag and drop an image into the div.
The img.src changes
Once theimg is loaded, i resize and clear the canvas
I draw the new img on the canvas
Function pointillize takes the new canvas data and uses it in the animation which it draws onto the canvas
I thought it had something to do with clearRect() and I tried implementing some of the suggestions but I don't think it's that. I added a click event listener to the document and it does indeed clear the canvas.
Could it have something to do with setInterval? I'm just thinking now that maybe, when the new image is loaded it triggers setInterval to run concurrently to the previous setInterval()? Maybe the solution is to kill all previous setInterval functions upon loading a new img?
EDIT: It works perfectly now. I declared draw a global variable, and inside function pointillize() I clearInterval(draw) before I set draw = setInterval(). This wipes the setInterval function clean before I start a new one.
html:
<div id='container'>
<img id='output' src='file.jpg'/>
</div>
javascript
var container, output;
var c, ctx, draw; // draw is declared here as a global variable
window.onload = function(e) {
c = document.createElement('canvas');
ctx = c.getContext('2d');
document.getElementById('container').appendChild(c);
output = document.getElementById('output');
output.width = '400';
container = document.getElementById('container');
container.style.border = '2px dashed rgb(200, 200, 200)';
container.style.padding = '6px';
container.addEventListener('dragenter', function(e) {
e.stopPropagation();
e.preventDefault();
this.style.border = '2px dashed rgb(50, 50, 50)';
}, false);
container.addEventListener('dragover', function(e) {
e.stopPropagation();
e.preventDefault();
this.style.border = '2px dashed rgb(50, 50, 50)';
}, false);
container.addEventListener('dragleave', function(e) {
e.stopPropagation();
e.preventDefault();
this.style.border = '2px dashed rgb(200, 200, 200)';
}, false);
container.addEventListener('drop', function(e) {
e.stopPropagation();
e.preventDefault();
this.style.border = '2px dashed rgb(200, 200, 200)';
var reader = new FileReader();
reader.addEventListener('load', function(e) {
output.src = e.target.result;
// once the new src img is loaded
output.onload = function() {
c.width = output.width;
c.height = output.height;
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(output, 0, 0, output.width, output.height);
pointillize(ctx);
}
}, false);
reader.readAsDataURL(e.dataTransfer.files[0]); // this line!
}, false);
}
function pointillize(context) {
clearInterval(draw); // before a new animation, clear setInterval
// get new image data
var imgData = context.getImageData(0, 0, c.width, c.height);
// clear canvas
ctx.beginPath();
ctx.rect(0, 0, output.width, output.height);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fill();
// store the setInterval in a variable so it can be cleared
var draw = setInterval(function() {
for (var i = 0; i < 100; i++) {
// pick random integers x and y
var y = Math.floor(Math.random()*output.height);
var x = Math.floor(Math.random()*output.width);
// get its position in the array
var loc = (y*output.width + x)*4;
// alpha in rgba goes from 0 to 1
var r = imgData.data[loc];
var g = imgData.data[loc+1];
var b = imgData.data[loc+2];
var a = 0.5;
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2*Math.PI);
ctx.fillStyle = 'rgb(' + r + ',' + g + ', ' + b + ',' + a + ')';
ctx.fill()
}
}, 50); // closing setInterval()
}
document.addEventListener('click', clearCanvas, false);
function clearCanvas() {
//interesting, so clearRect is working, for one frame...
ctx.clearRect(0, 0, c.width, c.height);
}

Related

Canvas text rendering

I can really use some help, my goal is to add few lines to the canvas in different places, it means to use the function drawText few times, for some reason if I don't use drawText inside the onload of drawImage the text does not rendering I am really stuck and can use some help, my main target it to make website that can edit pictures and make memes (e.g.: https://imgflip.com/memegenerator)and adding text line is what i am getting stuck on because I don't understand how to render new text line while save the old one, every time i start new line its like the image rendering all over again and start from the beginning.
// Function to load image on the canvas
function drawImg(url) {
gCurrLineIdx = getCurrLineIdx();
let currLine = gMeme.lines[gCurrLineIdx];
const img = new Image();
img.src = url;
img.onload = () => {
gCtx.drawImage(img, 0, 0, gElCanvas.width, gElCanvas.height);
//If you dont call drawText the text does not render to the canvas
drawText(currLine.txt, currLine.size, currLine.fontColor, currLine.strokeColor, currLine.align, currLine.font, gElCanvas.width / 2, currLine.y);
};
}
// Function to add text on the canvas
function drawText(text = '', fontSize = 20, fontColor = 'white', strokeColor = 'black', align = 'center', font = "ariel", x = gElCanvas.width / 2, y = 20) {
gCtx.strokeStyle = strokeColor;
gCtx.fillStyle = fontColor;
gCtx.font = `${fontSize}px ${font}`;
gCtx.textAlign = align;
gCtx.fillText(text, x, y);
gCtx.strokeText(text, x, y);
}
//Function to add new text line on the canvas.
function onAddTextLine() {
let textInput = document.getElementById('text-input');
textInput.value = '';
addTextLine();
gCurrLineIdx = getCurrLineIdx();
var currLine = gMeme.lines[gCurrLineIdx];
drawText(currLine.txt, currLine.size, currLine.fontColor, currLine.strokeColor, currLine.align, currLine.font, gElCanvas.width / 2, currLine.y);
}
The question I see here is:
how to render new text line while save the old one
Looks like in your code you are drawing things independently, below is a different approach the class Meme has a few functions to add text and image but every time anything change the entire canvas is cleared (clearRect) and every element is re-drawn, the magic is in the this.draw(), that is the call to the common function that does all the drawing.
To keep my sample small I hard-coded many of the things you had as parameters, it should be easy for you to reintroduce them if you need
class Meme {
constructor(ctx, width, height) {
this.ctx = ctx;
this.ctx.font = '80px Arial';
this.ctx.fillStyle = "blue"
this.ctx.textAlign = "center"
this.width = width;
this.height = height;
}
addHeadText(text) {
this.htext = text;
this.draw();
}
addFootText(text) {
this.ftext = text;
this.draw();
}
addImage(image_src) {
this.image = new Image();
this.image.src = image_src;
this.image.onload = () => {
this.draw()
};
}
draw() {
this.ctx.beginPath();
this.ctx.clearRect(0, 0, this.width, this.height)
if (this.image) {
this.ctx.drawImage(this.image, 0, 0, this.width, this.height);
}
if (this.htext) {
this.ctx.textBaseline = "top";
this.ctx.fillText(this.htext, this.width / 2, 0);
}
if (this.ftext) {
this.ctx.textBaseline = "bottom";
this.ctx.fillText(this.ftext, this.width / 2, this.height);
}
this.ctx.closePath();
}
}
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
meme = new Meme(ctx, canvas.width, canvas.height)
meme.addHeadText("Hello");
meme.addFootText("World");
meme.addImage("http://i.stack.imgur.com/UFBxY.png");
document.getElementById('htext').onkeyup = (event) => {
meme.addHeadText(event.srcElement.value);
};
document.getElementById('ftext').onkeyup = (event) => {
meme.addFootText(event.srcElement.value);
};
Head Text:<input type="text" id="htext" name="ftext" style="width: 140px;" value="Hello"><br>
Foot Text:<input type="text" id="ftext" name="ftext" style="width: 140px;" value="World"><br>
<canvas id="c" width=280 height=380 style="border:2px solid red;"></canvas>

How to store and restore canvas to use it again?

I am using PDF.js to show PDF in browser. PDF.js uses canvas to render PDF. I have js scripts that draws the lines on the canvas when user double clicks on the canvas. It also adds X check mark to remove the already drawn line.
based on my research i cannot simply just remove the line from the canvas because underneath pixels are gone when you draw something on it. To get it working i have to store lines and then clear canvas and re-load canvas and re-draw lines
Issue
I am not able to store canvas and restore canvas. When i click on X i was able to get lines re-drawn but canvas does not get restored. Canvas remains blank
Run the demo in full page
$(function () {
var $canvas = $("#myCanvas");
var canvasEl = $canvas.get(0);
var ctx = canvasEl.getContext("2d");
var lines = [];
var backupCanvas = document.createElement("canvas");
var loadingTask = pdfjsLib.getDocument('https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf');
loadingTask.promise.then(function (doc) {
console.log("This file has " + doc._pdfInfo.numPages + " pages");
doc.getPage(1).then(page => {
var scale = 1;
var viewPort = page.getViewport(scale);
canvasEl.width = viewPort.width;
canvasEl.height = viewPort.height;
canvasEl.style.width = "100%";
canvasEl.style.height = "100%";
var wrapper = document.getElementById("wrapperDiv");
wrapper.style.width = Math.floor(viewPort.width / scale) + 'px';
wrapper.style.height = Math.floor(viewPort.height / scale) + 'px';
page.render({
canvasContext: ctx,
viewport: viewPort
});
storeCanvas();
});
});
function storeCanvas() {
backupCanvas.width = canvasEl.width;
backupCanvas.height = canvasEl.height;
backupCanvas.ctx = backupCanvas.getContext("2d");
backupCanvas.ctx.drawImage(canvasEl, 0, 0);
}
function restoreCanvas() {
ctx.drawImage(backupCanvas, 0, 0);
}
$canvas.dblclick(function (e) {
var mousePos = getMousePos(canvasEl, e);
var line = { startX: 0, startY: mousePos.Y, endX: canvasEl.width, endY: mousePos.Y, pageY: e.pageY };
lines.push(line);
drawLine(line, lines.length - 1);
});
function drawLine(line, index) {
// draw line
ctx.beginPath();
ctx.strokeStyle = '#df4b26';
ctx.moveTo(line.startX, line.startY);
ctx.lineTo(line.endX, line.endY);
ctx.closePath();
ctx.stroke();
// add remove mark
var top = line.pageY;
var left = canvasEl.width + 20;
var $a = $("<a href='#' class='w-remove-line'>")
.data("line-index", index)
.attr("style", "line-height:0")
.css({ top: top, left: left, position: 'absolute' })
.html("x")
.click(function () {
var index = $(this).data("line-index");
$(".w-remove-line").remove();
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
// restore canvas
restoreCanvas();
lines.splice(index, 1);
for (var i = 0; i < lines.length; i++) {
drawLine(lines[i], i);
}
});
$("body").append($a);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
X: Math.floor(evt.clientX - rect.left),
Y: Math.floor(evt.clientY - rect.top),
};
}
});
canvas {
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<b> Double Click on PDF to draw line and then click on X to remove lines</b>
<div id="wrapperDiv">
<canvas id="myCanvas"></canvas>
</div>
The PDF.js render() function is async so you need to store the canvas after the render has finished. Your code is firing storeCanvas() too early and storing a blank canvas. Easy fix, render() returns a promise so ...
page.render({
canvasContext: ctx,
viewport: viewPort
}).then( () => {
storeCanvas();
});
https://jsfiddle.net/fyLant01/1/
Reference: from https://github.com/mozilla/pdf.js/blob/master/src/display/api.js#L998
/**
* Begins the process of rendering a page to the desired context.
* #param {RenderParameters} params Page render parameters.
* #return {RenderTask} An object that contains the promise, which
* is resolved when the page finishes rendering.
*/

HTML Canvas: ctx.stroke restroke behaviour with transparent colors

I am working on a sketch tool with html canvas.
I am using a common algorithm for this, that uses the mousedown, mousemove, mouseup events.
mousedown
I beginPath(), and moveTo(// the mouse coordinates).
mousemove
I draw lineTo(// the mouse coordinates), and then stoke(// the line to render it)
mouseup
I do nothing, // closePath()
And I noticed that, calling the stroke method without first calling closePath or beginPath, will redraw or restroke all previous paths or lines, which makes them appear thicker than the define color.
without a transparent color its is barely noticeable, but the colors do appear thicker than they should be.
but with color with transparency|alpha e.g. rgba(). The most recent path or line respects the color's transparency, but all previous line due to the redraw, the transparent colored line overlap and that causes previous lines to get thicker in sequence or succession.
is there a way to avoid|prevent this behavior. thank in advance.
sample is below, try drawing really fast!
var cvs = document.querySelector("canvas");
cvs.width = cvs.parentElement.clientWidth;
var colorInput = document.querySelector("input");
var ctx = cvs.getContext("2d");
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
ctx.lineWidth = 20;
onDraw(cvs, {
penDown: function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
ctx.strokeStyle = colorInput.value;
ctx.beginPath();
ctx.moveTo(x, y);
},
penMove: function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
ctx.lineTo(x, y);
ctx.stroke();
},
penUp: function() {
// ctx.closePath;
}
});
function onDraw(node, drawHandler, beginHandler, endHandler, outOfBoundHandler, sticky) {
var mouseDown = false, mouseOut = false;
if( typeof drawHandler === "object" ) {
var drawEvents = drawHandler;
drawHandler = get(drawEvents.penMove);
beginHandler = get(drawEvents.penDown);
endHandler = get(drawEvents.penUp);
outOfBoundHandler = get(drawEvents.penOff);
sticky = drawEvents.sticky;
}
function get(name) {
return typeof name === "string" ? drawEvents[ name ] : name;
}
node.addEventListener('mousedown', function(e) {
mouseDown = true;
beginHandler&&beginHandler.call(this, e);
});
node.addEventListener('mousemove', function(e) {
mouseDown&&drawHandler&&drawHandler.call(this, e);
});
node.addEventListener('mouseup', function(e) {
mouseDown = false;
endHandler&&endHandler.call(this, e);
});
node.addEventListener('mouseout', function(e) {
mouseDown&&outOfBoundHandler&&outOfBoundHandler.call(this, e);
if( !sticky ) {
mouseDown = false;
}
});
}
.wrapper { border: 1px solid #aaa }
<div class="wrapper">
<canvas border="1" width="600" hieght="400">Canvas is not supported</canvas>
<input type="text" value="rgba(0, 0, 0, 0.3)" placeholder="rgba(#, #, #, #)">
</div>
If no Path argument is passed to stroke and fill methods they will use the path currently being declared with the context's drawing methods.
const ctx = c.getContext('2d');
// starts Path declaration
ctx.moveTo(20, 20);
ctx.lineTo(30, 80);
ctx.stroke(); // first rendering
setTimeout(() => {
ctx.clearRect(0, 0, 300, 150); // even if we clear the canvas
ctx.lineTo(70, 20); // this will continue path declaration
setTimeout(() => {
ctx.stroke(); // and this will draw everything
}, 1000);
}, 1000);
<canvas id="c"></canvas>
The only ways to start a new path declaration (except for the first one) are to either reset the whole context (not good), or to use beginPath method.
const ctx = c.getContext('2d');
// starts Path declaration
ctx.moveTo(20, 20);
ctx.lineTo(30, 80);
ctx.stroke(); // first rendering
setTimeout(() => {
ctx.clearRect(0, 0, 300, 150);
ctx.beginPath(); // start a new Path declaration
ctx.moveTo(30, 80); // we need to move to the previous coords
ctx.lineTo(70, 20); // this will be alone
ctx.stroke(); // and this will draw only the new path
}, 1000);
<canvas id="c"></canvas>
About closePath, it's just a lineTo(last_point_in_current_path_declaration), and doesn't ends a path declaration in no way.
So for your problem, there are two strategies you can adopt :
keep only the last coordinates, and at every mousemove,
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(nextX, nextY);
keep all your coordinates in an array and redraw everything every time
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
coords.forEach(pt => ctx.lineTo(pt.x, pt.y));
ctx.stroke();
Personally, I prefer the second one, which allows some undo - redo, and to e.g change your pen's style.

Drawing another image to canvas

So I am creating a cordova app where I take a photo from the iphone library, draw it to canvas and add another image to it in order to save it as one photo. So far the photo I draw from the iphone photo library gets drawn without problems to the canvas, however the second image doesn't.
When I load the second image, it first gets added to a div with absolute positioning in order to move it to wherever I want. After that I get the actual image it's source and it's positions and try to draw it to canvas. The drawing of the second image happens when I call a method that also performs the canvas2ImagePlugin it's functions. In the end only the first photo without the second image gets saved.
The draw image to canvas function:
function drawImage(image_source, dx, dy)
{
var canvas = document.getElementById('Photo');
var image = new Image();
image.src = image_source;
image.onload = function() {
c=canvas.getContext("2d");
c.canvas.width = window.innerWidth;
c.canvas.height = window.innerHeight;
c.drawImage(image,dx,dy, window.innerWidth, window.innerHeight);
}
}
The method for drawing the second image and saving it:
function saveImage()
{
var img = $('.ObjectImage').attr('src', $('img:first').attr('src'));
var imagePosition = $('.ObjectImage').find('img:first').position();
drawImage(img, imgPosition.left, imgPosition.top);
window.canvas2ImagePlugin.saveImageDataToLibrary(
function(msg){
console.log(msg);
},
function(err){
console.log(err);
},
document.getElementById('Photo')
);
alert("Image saved");
}
The window.innerWidth, window.innerHeight on the canvas is done to get the canvas in full screen of the parent div.
EDIT to the comment:
function drawImage(image_source, dx, dy)
{
var canvas = document.getElementById('Photo');
var image = new Image();
image.onload = function() {
c=canvas.getContext("2d");
c.canvas.width = window.innerWidth;
c.canvas.height = window.innerHeight;
c.drawImage(image,dx,dy, window.innerWidth, window.innerHeight);
}
image.src = image_source;
}
Still not working
The drawImage function works asynchronously, it starts loading an image and exits immediately. Then, when the image loads, the canvas is updated. If you run something like:
drawImage('test.jpg',0,0);
drawImage('test2.jpg',0,0);
you will get both images updating the canvas at approximately the same time and only one will appear.
Also, what wolfhammer said is correct. If you set the size of the canvas, you clear it, so drawing one image after the other, even if they are smaller sizes and should both appear, will only show the last one. Check this link on how to solve it: Preventing Canvas Clear when Resizing Window
Further more, you are drawing all images with the width and height of the window, which doesn't make sense. Probably you want to use the width and height of the image (so this.width instead of window.innerWidth)
When you set the width and height of the canvas the data on "Photo" is cleared. I've provide a resize function if resizing is really needed.
function drawImage(image_source, dx, dy)
{
var canvas = document.getElementById('Photo');
var image = new Image();
image.src = image_source;
image.onload = function() {
c=canvas.getContext("2d");
//c.canvas.width = window.innerWidth;
//c.canvas.height = window.innerHeight;
c.drawImage(image,dx,dy, window.innerWidth, window.innerHeight);
}
}
var can = document.getElementById('can');
var ctx = can.getContext('2d');
ctx.fillStyle = "red";
ctx.beginPath();
ctx.moveTo(20, 90);
ctx.lineTo(50, 10);
ctx.lineTo(80, 90);
ctx.lineTo(10, 40);
ctx.lineTo(90, 40);
ctx.lineTo(20, 90);
ctx.fill();
var btn = document.getElementById('btnResize');
btn.addEventListener('click', function() {
resize(can, can.width * 2, can.height * 2);
});
function resize(can, w, h) {
var ctx = can.getContext('2d');
// copy
var can2 = document.createElement('canvas');
var ctx2 = can2.getContext('2d');
can2.width = can.width;
can2.height = can.height;
ctx2.drawImage(can, 0, 0);
// resize
can.width = w;
can.height = h;
ctx.drawImage(can2, 0, 0, can2.width, can2.height, 0, 0, w, h);
}
#can {
border:1px solid red;
}
<button id='btnResize'>Size x 2</button><br/>
<canvas id="can" width="100" height="100"></canvas>

Take Keyboard Input Into Ember Component

I've created an ember component with a rectangular block inside a green canvas.
What I'm having trouble with is adding a keyboard-input command for A S D W to move the rectangle around the canvas. It's easy enough to do in regular javascript or jquery but inside the component model I'm a bit lost. Any help regarding the function would be very useful.
Linked here is an ember javascript bin: http://emberjs.jsbin.com/miyatoti/1/edit
Here is my present code of the component.
App.BoxGameComponent = Em.Component.extend({
tagName:'canvas',
width: 325,
height: 280,
refresh:30,
attributeBindings:['width', 'height'],
stars:null,
on:false,
build: function(){
var canvas = this.$()[0],
ctx = canvas.getContext('2d'),
shippos = [150, 120],
height = this.get('height'),
width = this.get('width');
this.set('shippos', shippos);
this.set('ctx', ctx);
this.set('on', true);
}.on('didInsertElement'),
kill: function(){
this.set('on', false);
}.on('willDestroyElement'),
clear: function () {
var ctx = this.get('ctx'),
height = this.get('height'),
width = this.get('width');
ctx.fillStyle = 'green';
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.rect(0, 0, width, height);
ctx.closePath();
ctx.fill();
},
box: function () {
var that = this;
var ctx = this.get('ctx'),
height = this.get('height'),
width = this.get('width'),
shippos = this.get('shippos');
var posx = shippos[0],
posy = shippos[1];
ctx.rect(posx,posy,50,50);
ctx.stroke();
},
game: function(){
if(this.get('on')){
this.loop();
}
}.observes('on'),
loop: function () {
var refreshRate = this.get('refresh');
this.clear();
this.box();
if(this.get('on')){
Em.run.later(this, this.loop, refreshRate);
}
}
});
If anyone can help I've been slamming my brain at this for hours.
Hooking up keyup a canvas element is a bit trickier since the canvas doesn't get focus. So you just hook up to the window (and then destroy it later).
$(window).on('keyup', {self:this}, this.handleKeyUp );
http://emberjs.jsbin.com/miyatoti/2/edit

Categories

Resources