I am having trouble with my javascript animation - javascript

I am trying to animate using a sprite sheet and use it in my website, however I am knew to animation and I am having some trouble fixing it, currently nothing shows up on my webpage when I use this. I am using a sprite sheet which is called Speech.png, is 18176 x 256 and each frame is 256x256. Here is my code so far.
<script language="javascript">
// screen size variables
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
var xpos=0,
ypos=0,
index=0,
numFrames = 70,
frameSize= 255;
// Add our drawing canvas
document.body.appendChild(canvas);
//load the image
image = new Image();
image.src = "Speech.png";
image.onload = function() {
//we're ready for the loop
setInterval(loop, 1000 / 30);
}
function loop() {
//clear the canvas!
c.clearRect(0,0, SCREEN_HEIGHT,SCREEN_WIDTH);
/*our big long list of arguments below equates to:
1: our image source
2 - 5: the rectangle in the source image of what we want to draw
6 - 9: the rectangle of our canvas that we are drawing into
the area of the source image we are drawing from will change each time loop() is called.
the rectangle of our canvas that we are drawing into however, will not.
tricky!
*/
c.drawImage(image,xpos,ypos,frameSize,frameSize,0,0,frameSize, frameSize);
//each time around we add the frame size to our xpos, moving along the source image
xpos += frameSize;
//increase the index so we know which frame of our animation we are currently on
index += 1;
//if our index is higher than our total number of frames, we're at the end and better start over
if (index >= numFrames) {
xpos = 0;
ypos = 0;
index = 0;
//if we've gotten to the limit of our source image's width, we need to move down one row of frames
} else if (xpos + frameSize > image.width){
xpos =0;
ypos += frameSize;
}
}
</script>

Related

How to properly Down-sampling HTML Canvas for good looking images?

Introduction
I'm trying to deal with blurry visuals on my canvas animation. The blurriness is especially prevalent on mobile-devices, retina and high-dpi (dots-per-inch) screens.
I'm looking for a way to ensure the pixels that are drawn using the canvas look their best on low-dpi screens and high-dpi screens. As a solution to this problem I red multiple articles about canvas-down-scaling and followed this tutorial:
https://www.kirupa.com/canvas/canvas_high_dpi_retina.htm
Integrating down-scaling in the project
The project in which I want to implement down-scaling can be found below and consists of a few important features:
There is a (big) main canvas. (Performance optimization)
There are multiple (pre-rendered) smaller canvasses that are used to draw and load a image into. (Performance optimization)
The canvas is animated. (In the code snippet, there is no visible animation but the animation function is intergrated.)
Question
What im trying to achieve: The problem I'm facing seems quite simple. When the website (with the canvas) is opened on a mobile device (eg. an Iphone, with more pixels per inch then a regular desktop). The images appear more blurry. What I'm actually trying to achieve is to remove this blurriness from the images. I red this and it stated that blurriness can be removed by downsampling. I tried to incorporate this technique in the code provided, but it did not work completely. The images just became larger and I was unable to scale the images back to the original size. snippet it is not implemented correctly, the output is still blurry. What did I do wrong and how am I able to fix this issue?
Explanation of the code snippet
The variable devicePixelRatio is set to 2 to simulate a high-dpi phone screen, low-dpi screens have a devicePixelRatio of 1.
Multiple pre-rendered canvasses generated is the function spawn is the snippet there are 5 different canvasses, but on the production environment there are 10's.
If there are any pieces of information missing or questions about this post, please let me know. Thanks a lot!
Code Snippet
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d' );
var circles = [];
//Simulate Retina screen = 2, Normal screen = 1
let devicePixelRatio = 2
function mainCanvasPixelRatio() {
// get current size of the canvas
let rect = canvas.getBoundingClientRect();
// increase the actual size of our canvas
canvas.width = rect.width * devicePixelRatio;
canvas.height = rect.height * devicePixelRatio;
// ensure all drawing operations are scaled
c.scale(devicePixelRatio, devicePixelRatio);
// scale everything down using CSS
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';
}
// Initial Spawn
function spawn() {
for (let i = 0; i < 2; i++) {
//Set Radius
let radius = parseInt(i*30);
//Give position
let x = Math.round((canvas.width/devicePixelRatio) / 2);
let y = Math.round((canvas.height /devicePixelRatio) / 2);
//Begin Prerender canvas
let PreRenderCanvas = document.createElement('canvas');
const tmp = PreRenderCanvas.getContext("2d");
//Set PreRenderCanvas width and height
let PreRenderCanvasWidth = ((radius*2)*1.5)+1;
let PreRenderCanvasHeight = ((radius*2)*1.5)+1;
//Increase the actual size of PreRenderCanvas
PreRenderCanvas.width = PreRenderCanvasWidth * devicePixelRatio;
PreRenderCanvas.height = PreRenderCanvasHeight * devicePixelRatio;
//Scale PreRenderCanvas down using CSS
PreRenderCanvas.style.width = PreRenderCanvasWidth + 'px';
PreRenderCanvas.style.height = PreRenderCanvasHeight + 'px';
//Ensure PreRenderCanvas drawing operations are scaled
tmp.scale(devicePixelRatio, devicePixelRatio);
//Init image
const image= new Image();
//Get center of PreRenderCanvas
let m_canvasCenterX = (PreRenderCanvas.width/devicePixelRatio) * .5;
let m_canvasCenterY = (PreRenderCanvas.height/devicePixelRatio) * .5;
//Draw red circle on PreRenderCanvas
tmp.strokeStyle = "red";
tmp.beginPath();
tmp.arc((m_canvasCenterX), (m_canvasCenterY), ((PreRenderCanvas.width/devicePixelRatio)/3) , 0, 2 * Math.PI);
tmp.lineWidth = 2;
tmp.stroke();
tmp.restore();
tmp.closePath()
//Set Image
image .src= "https://play-lh.googleusercontent.com/IeNJWoKYx1waOhfWF6TiuSiWBLfqLb18lmZYXSgsH1fvb8v1IYiZr5aYWe0Gxu-pVZX3"
//Get padding
let paddingX = (PreRenderCanvas.width/devicePixelRatio)/5;
let paddingY = (PreRenderCanvas.height/devicePixelRatio)/5;
//Load image
image.onload = function () {
tmp.beginPath()
tmp.drawImage(image, paddingX,paddingY, (PreRenderCanvas.width/devicePixelRatio)-(paddingX*2),(PreRenderCanvas.height/devicePixelRatio)-(paddingY*2));
tmp.closePath()
}
let circle = new Circle(x, y, c ,PreRenderCanvas);
circles.push(circle)
}
}
// Circle parameters
function Circle(x, y, c ,m_canvas) {
this.x = x;
this.y = y;
this.c = c;
this.m_canvas = m_canvas;
}
//Draw circle on canvas
Circle.prototype = {
//Draw circle on canvas
draw: function () {
this.c.drawImage( this.m_canvas, (this.x - (this.m_canvas.width)/2), (this.y - this.m_canvas.height/2));
}
};
// Animate
function animate() {
//Clear canvas each time
c.clearRect(0, 0, (canvas.width /devicePixelRatio), (canvas.height /devicePixelRatio));
//Draw in reverse for info overlap
circles.slice().reverse().forEach(function( circle ) {
circle.draw();
});
requestAnimationFrame(animate);
}
mainCanvasPixelRatio()
spawn()
animate()
#mainCanvas {
background:blue;
}
<canvas id="mainCanvas"></canvas>
<br>
<!DOCTYPE html>
<html>
<body>
<p>Image to use:</p>
<img id="scream" width="220" height="277"
src="pic_the_scream.jpg" alt="The Scream">
<p>Canvas:</p>
<canvas id="myCanvas" width="240" height="297"
style="border:1px solid #d3d3d3;">
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var img = document.getElementById("scream");
ctx.drawImage(img, 10, 10);
};
</script>
</body>

CanvasRenderingContext2D putImageData oddity

So i'm adding some image manipulation functions to one of our company projects. Part of the feature is an image cropper with the desire to 'auto-detect' the cropped image to some degree. If our guess is bad they can just drag & drop the cropper points, but most images people should be able to be auto-cropped.
My issue is when i'm putting the data back into the canvas indexes that work don't seem make any sense to me based on the documentation. I'm trying to take the rect I find and convert he canvas to a single image size that will now contain my whole rect.
let width = right - left + 1, height = bottom - top + 1;
canvas.width = width;
canvas.height = height;
ctx.putImageData(imageBuffer, -left, -top, left, top, width,height);
This gives me the correct image. I would have expected based on the documentation that the below code would be correct. I verified in mspaint that my indexes for the rect are correct so I know it isn't my algorithm coming up with weird numbers.
let width = right - left + 1, height = bottom - top + 1;
canvas.width = width;
canvas.height = height;
ctx.putImageData(imageBuffer, 0, 0, left, top, width,height);
Why do you have to put a negative indexing for the 2nd & 3rd argument? I've verified it behaves like this in both Chrome & Firefox.
Yes, it might be a bit confusing, but when you putImageData, the destinationWidth and destinationHeight you would have in e.g drawImage, are always equal to the ImageData's width and height.
The 4 last params of putImageData(), dirtyX, dirtyY, dirtyWidth and dirtyHeight values are relative to the ImageData's boundaries.
So with the first two params, you just set the position of the ImageData's boundaries, with the 4 others, you set the position of your pixels in this ImageData's boundary.
var ctx = canvas.getContext('2d');
var imgBound = {
x: 10,
y: 10,
width: 100,
height: 100
},
innerImg = {
x: 20,
y: 20,
width: 200,
height: 200
};
// a new ImageData, the size of our canvas
var img = ctx.createImageData(imgBound.width, imgBound.height);
// fill it with noise
var d = new Uint32Array(img.data.buffer);
for(var i=0;i<d.length; i++)
d[i] = Math.random() * 0xFFFFFFFF;
function draw() {
ctx.putImageData(img,
imgBound.x,
imgBound.y,
innerImg.x,
innerImg.y,
innerImg.width,
innerImg.height
);
// the ImageData's boundaries
ctx.strokeStyle = 'blue';
ctx.strokeRect(imgBound.x, imgBound.y, imgBound.width, imgBound.height);
// our pixels boundaries relative to the ImageData's bbox
ctx.strokeStyle = 'green';
ctx.strokeRect(
// for stroke() we need to add the ImageData's translation
innerImg.x + imgBound.x,
innerImg.y + imgBound.y,
innerImg.width,
innerImg.height
);
}
var inner_direction = -1,
imgBound_direction = -1;
function anim() {
innerImg.width += inner_direction;
innerImg.height += inner_direction;
if(innerImg.width <= -50 || innerImg.width > 200) inner_direction *= -1;
imgBound.x += imgBound_direction;
if(imgBound.x <= 0 || imgBound.x > 200)
imgBound_direction *= -1;
ctx.clearRect(0,0,canvas.width,canvas.height);
draw();
requestAnimationFrame(anim);
}
anim();
canvas{border: 1px solid;}
<canvas id="canvas" width="300" height="300"></canvas>

Fabric JS getContext('2d') not matching getImageData color

Website: http://minimedit.com/
Currently implementing an eye dropper. It works fine in my normal resolution of 1080p, but when testing on a higher or lower resolution it doesn't work.
This is the basics of the code:
var ctx = canvas.getContext("2d");
canvas.on('mouse:down', function(e) {
var newColor = dropColor(e, ctx);
}
function dropColor(e, ctx) {
var mouse = canvas.getPointer(e.e),
x = parseInt(mouse.x),
y = parseInt(mouse.y),
px = ctx.getImageData(x, y, 1, 1).data;
return rgb2hex('rgba('+px+')');
}
When I first initiate the canvas I have it resize to fit resolution:
setResolution(16/9);
function setResolution(ratio) {
var conWidth = ($(".c-container").css('width')).replace(/\D/g,'');
var conHeight = ($(".c-container").css('height')).replace(/\D/g,'');
var tempWidth = 0;
var tempHeight = 0;
tempHeight = conWidth / ratio;
tempWidth = conHeight * ratio;
if (tempHeight > conHeight) {
canvas.setWidth(tempWidth);
canvas.setHeight(conHeight);
} else {
canvas.setWidth(conWidth);
canvas.setHeight(tempHeight);
}
}
The x and y mouse coordinates work fine when zoomed in, but they don't line up with the returned image data. It seems as though the ctx isn't changing it's width and height and scaling along with the actual canvas size.
The canvas element is showing the correct width and height before using getContext as well.
Any ideas on a solution?
Feel free to check out the full scripts on the live website at: http://minimedit.com/
Try "fabric.devicePixelRatio" for calculating actual position, for example:
x = parseInt(mouse.x) * fabric.devicePixelRatio

HTML5 Canvas - drawing fails once shifting the current browser tab

Ok, I've noticed this bug happen not only in the following code(which is just to illustrate), but every code I've written for canvas;
basically, in chrome(I didn't test other browsers),
once you shift your current tab to either a different pc screen, or simply if you have a few tabs in a row and decide to make a new window out of your current tab,
the canvas itself fails to draw. it is stuck and no reason is given in the console.
Here is a GIF of it happening:
http://gifmaker.cc/PlayGIFAnimation.php?folder=2016081601ZEuvXaZnxC2T9fHNNqSW3l&file=output_f72O1H.gif
Just a simple canvas code if you want to try it out:
<canvas id="canvas" width="500" height="400" style="border:1px solid #000000;"></canvas>
<script>
// grab the canvas and context
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// inital coordinates of the black square
var x = 0;
var y = 200;
// speed of the movement in x- and y-direction
// actual no movement in y-direction
var vX = 1;
var vY = 0;
// width and height of the square
var width = 10;
var height = 10;
function animate() {
// clear
// comment this clear function to see the plot of the sine movement
ctx.clearRect(0, 0, canvas.width, canvas.height);
x += vX;
y += vY;
// if the block leaves the canvas on the right side
// bring it back to the left side
if(x>500) {
x = 0;
}
ctx.fillRect(x, y, width, height);
setTimeout(animate, 33);
}
// call the animate function manually for the first time
animate();
</script>
Some sites fixed this bug but I don't know if I am allowed to link them here.

How do I draw thin but sharper lines in html canvas?

I have the following javascript code to draw a graph sheet. But the problem is when I take a printout, The thin lines are not appearing sharp. The problem is visible when you zoom the html page. I want the lines to be more sharp. But the width should be the same. Is it possible? Please help.
function drawBkg(canvasElem, squareSize, minorLineWidthStr, lineColStr)
{
var nLinesDone = 0;
var i, curX, curY;
var ctx = canvasElem.getContext('2d');
ctx.clearRect(0,0,canvasElem.width,canvasElem.height);
// draw the vertical lines
curX=0;
ctx.strokeStyle = lineColStr;
while (curX < canvasElem.width)
{
if (nLinesDone % 5 == 0)
ctx.lineWidth = 0.7;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(curX, 0);
ctx.lineTo(curX, canvasElem.height);
ctx.stroke();
curX += squareSize;
nLinesDone++;
}
// draw the horizontal lines
curY=0;
nLinesDone = 0;
while (curY < canvasElem.height)
{
if (nLinesDone % 5 == 0)
ctx.lineWidth = 0.7;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(0, curY);
ctx.lineTo(canvasElem.width, curY);
ctx.stroke();
curY += squareSize;
nLinesDone++;
}
}
drawBkg(byId('canvas'), 3.78, "0.35", "green");
What you are experiencing is the difference between your screen's PPI and your printer's DPI.
Canvas output is a raster image, if you set its size to be like 96px, a monitor with a resolution of 96ppi will output it as a one inch large image, but a printer with 300ppi will output it as a 3.125 inch image.
When doing so, the printing operation will downsample your image so it can fit into this new size. (each pixel will be multiplied so it covers a bigger area).
But the canvas context2d has a scale() method, so if all your drawings are vector based1, you can :
create a bigger canvas before printing,
set its context's scale to the wanted factor,
call the same drawing as on the smaller canvas
if you are printing directly from the browser's "print the page", set the bigger canvas style.width and style.height properties to the width and height properties of the smaller one,
replace the smaller canvas node with the bigger one,
print,
replace the bigger canvas with the original one
For this, you will need to rewrite a little bit your function so it doesn't take the passed canvas' width/height as values, but rather values that you have chosen.
function drawBkg(ctx, width, height, squareSize, minorLineWidthStr, lineColStr) {
var nLinesDone = 0;
var i, curX, curY;
ctx.clearRect(0, 0, width, height);
// draw the vertical lines
curX = 0;
ctx.strokeStyle = lineColStr;
while (curX < width) {
if (nLinesDone % 5 == 0)
ctx.lineWidth = 0.7;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(curX, 0);
ctx.lineTo(curX, height);
ctx.stroke();
curX += squareSize;
nLinesDone++;
}
// draw the horizontal lines
curY = 0;
nLinesDone = 0;
while (curY < height) {
if (nLinesDone % 5 == 0)
ctx.lineWidth = 0.7;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(0, curY);
ctx.lineTo(width, curY);
ctx.stroke();
curY += squareSize;
nLinesDone++;
}
}
// your drawings
var smallCanvas = document.getElementById('smallCanvas');
var smallCtx = smallCanvas.getContext('2d');
drawBkg(smallCtx, smallCanvas.width, smallCanvas.height, 3.78, "0.35", "green");
// a function to get the screen's ppi
function getPPI() {
var test = document.createElement('div');
test.style.width = "1in";
test.style.height = 0;
document.body.appendChild(test);
var dpi = devicePixelRatio || 1;
var ppi = parseInt(getComputedStyle(test).width) * dpi;
document.body.removeChild(test);
return ppi;
}
function scaleAndPrint(outputDPI) {
var factor = outputDPI / getPPI();
var bigCanvas = smallCanvas.cloneNode();
// set the required size of our "printer version" canvas
bigCanvas.width = smallCanvas.width * factor;
bigCanvas.height = smallCanvas.height * factor;
// set the display size the same as the original one to don't brake the page's layout
var rect = smallCanvas.getBoundingClientRect();
bigCanvas.style.width = rect.width + 'px';
bigCanvas.style.height = rect.height + 'px';
var bigCtx = bigCanvas.getContext('2d');
// change the scale of our big context
bigCtx.scale(factor, factor);
// tell the function we want the height and width of the small canvas
drawBkg(bigCtx, smallCanvas.width, smallCanvas.height, 3.78, "0.35", "green");
// replace our original canvas with the bigger one
smallCanvas.parentNode.replaceChild(bigCanvas, smallCanvas);
// call the printer
print();
// set the original one back
bigCanvas.parentNode.replaceChild(smallCanvas, bigCanvas);
}
btn_o.onclick = function() { print(); };
btn_s.onclick = function() { scaleAndPrint(300);};
<button id="btn_o">print without scaling</button>
<button id="btn_s">print with scaling</button>
<br>
<canvas id="smallCanvas" width="250" height="500"></canvas>
1. all drawing operations on canvas are vector based, except for drawImage(), and putImageData()
Most simple way to achieve cripser lines is to use oversampling : you draw in a canvas which has a resolution bigger than the screen's resolution.
In Javascript if you want to oversample by a factor of X :
Change canvas's width and height to width*X and height*X
Scale the canvas's context by a factor of X
Fix Css width and height to inital width and height to keep same size on screen.
In the below sample i first downsampled the canvas to make it easier to see. You have to zoom quite a lot to see the difference between no upsampling, 2 X and 4X.
function overSampleCanvas(tgtCanvas, ctx, factor) {
var width = tgtCanvas.width;
var height = tgtCanvas.height;
tgtCanvas.width = 0 | (width * factor);
tgtCanvas.height = 0 | (height * factor);
tgtCanvas.style.width = width + 'px';
tgtCanvas.style.height = height + 'px';
ctx.scale(factor, factor);
}
// -------------------- example
var $ = document.getElementById.bind(document);
var cv05 = $('cv05'),
ctx05 = cv05.getContext('2d');
var cv = $('cv'),
ctx = cv.getContext('2d');
var cv2X = $('cv2X'),
ctx2X = cv2X.getContext('2d');
var cv4X = $('cv4X'),
ctx4X = cv4X.getContext('2d');
overSampleCanvas(cv05, ctx05, 0.5);
overSampleCanvas(cv2X, ctx2X, 2);
overSampleCanvas(cv4X, ctx4X, 4);
function drawCircle(ctx) {
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 6.28);
ctx.fillStyle = '#AB6';
ctx.fill();
}
drawCircle(ctx05);
drawCircle(ctx);
drawCircle(ctx2X);
drawCircle(ctx4X);
canvas downsampled by 2X, normal, then upsampled by 2X, then 4X. <br>
<canvas id="cv05" width="100" height="100"></canvas>
<canvas id="cv" width="100" height="100"></canvas>
<canvas id="cv2X" width="100" height="100"></canvas>
<canvas id="cv4X" width="100" height="100"></canvas>

Categories

Resources