Save() and Restore() function not working? - javascript

(function (canvasID, imgID) {
"use strict";
var canvas, ctx, myImg;
var initialize = function (){
canvas = document.getElementById(canvasID);
myImg = document.getElementById(imgID);
ctx = canvas.getContext('2d');
};
var renderImg = function (x, y, w, h, img, mixImg, filter){
if(ctx) {
ctx.drawImage(img, x, y, w, h);
mixImg(x, y, w, h, filter);
}
};
var mixImg = function (x, y, w, h, filter){
var r, g, b, a, v;
var canvasData = ctx.getImageData(x, y, w, h);
if(filter) {
switch(filter) {
case 'grayscale':
for (var i = 0; i < canvasData.data.length; i+=4){
r = canvasData.data[i];
g = canvasData.data[i+1];
b = canvasData.data[i+2];
v = 0.2126*r + 0.7152*g + 0.0722*b;
canvasData.data[i] = canvasData.data[i+1] = canvasData.data[i+2] = v;
}
break;
case 'retro':
for (var i = 0; i < canvasData.data.length; i+=4){
r = canvasData.data[i];
g = canvasData.data[i+1];
b = canvasData.data[i+2];
a = canvasData.data[i+3];
canvasData.data[i] = r-40;
canvasData.data[i+1] = g-50;
canvasData.data[i+2] = b+23;
canvasData.data[i+3] = 200;
}
break;
case 'instagram':
for (var i = 0; i < canvasData.data.length; i+=4){
r = canvasData.data[i];
g = canvasData.data[i+1];
b = canvasData.data[i+2];
canvasData.data[i] = r+63;
canvasData.data[i+1] = g+41;
canvasData.data[i+2] = 60;
}
break;
} // end of switch
} // end of if
ctx.putImageData(canvasData, x, y);
};
window.onload = function () {
initialize();
if(canvas && canvas.getContext) {
renderImg(0, 0, 250, 250, myImg, mixImg);
ctx.save();
renderImg(250, 0, 250, 250, myImg, mixImg, 'grayscale');
ctx.save();
renderImg(0, 250, 250, 250, myImg, mixImg, 'retro');
ctx.save();
renderImg(250, 250, 250, 250, myImg, mixImg, 'instagram');
ctx.save();
ctx.translate(0, 500);
ctx.restore();
}
};
})('collage', 'img');
How can I mirror the whole canvas(the 4 four images I rendered) I drew and place it on x:0 y:500?
I tried to save each image I drew and then translate it on different point and then restore it.
But nothing shown except then 4 images I drew. What did I do wrong??

translate only change reference point.
After ctx.translate(0, 500) x = 0 is upper left corner while y = 0 is 500 down.
You have to paint or put data into the canvas as well.
However getImageData and putImageData is not affected by transformation matrix so one have to say e.g.:
var trans = [0, 500];
ctx.putImageData(
canvasData,
x + trans[0],
y + trans[1]
);
or put the data into a memory canvas and use drawImage().
Ref.:
The current path, transformation matrix, shadow attributes, global alpha, the clipping region, and global composition operator must not affect the getImageData() and putImageData() methods.
As your code is now you also do a draw + read + redraw onto same region. You could use one draw and then use that as source for all other. There is also no need for the save and restore.
Edit:
Memory canvas as in document.createElement('CANVAS'). It all depends on how and for what purpose etc. A possible set-up could be.
var img = {}, memc = {}, domc = {};
img.src = document.getElementById(imgId);
img.w = img.src.width;
img.h = img.src.height;
/* DOM Canvas */
domc.can = document.getElementById(canvasId);
domc.ctx = domc.can.getContext('2d');
domc.width = img.w * 2; // Going to duplicate image 2 columns.
domc.height = img.h * 4; // Going to duplicate image 4 rows.
/* Memory Canvas */
memc.can = document.createElement('CANVAS');
memc.ctx = memc.can.getContext('2d');
memc.width = img.w;
memc.height = img.h;

Related

Diagonal lines on canvas are drawing with different color/opacity

I'm drawing diagonal lines on an HTML canvas with a specific size, but some of them appear to have different color/opacity than the others. I would like them to have the same color/opacity.
Diagonal lines picture
The code I'm using to generate this output is the following:
let artist = {
step: 50,
distanceBetweenLines: 10,
verticalDifferenceInLines: 150,
}
window.onload = function () {
canv = document.getElementById("gc");
ctx = canv.getContext("2d");
ctx.translate(0.5, 0.5);
ctx.lineWidth = "1";
ctx.strokeStyle = "black";
draw();
}
function draw () {
let step = artist.step;
let d = artist.distanceBetweenLines;
let v = artist.verticalDifferenceInLines;
for (let x = 0; x < 500; x += step) {
for (let y = 0; y < 500; y += step) {
let increment = 30;
line({x:x, y: y}, {x:x+increment, y: y+increment});
}
}
}
function line(init, end) {
ctx.beginPath();
ctx.moveTo(init.x, init.y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
}
Why I'm getting this effect on some of the lines?
This is a chrome bug. I opened an issue here.
That's basically a problem with Hardware Acceleration, if you disable it, it will render nicely even in Chrome.
To workaround the issue, you can compose a single bigger path which will contain all your lines and call stroke() only once:
let artist = {
step: 50,
distanceBetweenLines: 10,
verticalDifferenceInLines: 150,
}
window.onload = function() {
canv = document.getElementById("gc");
ctx = canv.getContext("2d");
ctx.translate(0.5, 0.5);
ctx.lineWidth = "1";
ctx.strokeStyle = "black";
draw();
}
function draw() {
let step = artist.step;
let d = artist.distanceBetweenLines;
let v = artist.verticalDifferenceInLines;
// a single big path
ctx.beginPath();
for (let x = 0; x < 500; x += step) {
for (let y = 0; y < 500; y += step) {
let increment = 30;
line({
x: x,
y: y
}, {
x: x + increment,
y: y + increment
});
}
}
// stroke only once
ctx.stroke();
}
function line(init, end) {
ctx.moveTo(init.x, init.y);
ctx.lineTo(end.x, end.y);
}
<canvas id="gc" width="500" height="500"></canvas>
But for this exact drawing, it would even be better to use a CanvasPattern:
// An helper function to create CanvasPatterns
// returns a 2DContext on which a simple `finalize` method is attached
// method which does return a CanvasPattern from the underlying canvas
function patternMaker( width, height ) {
const canvas = document.createElement( 'canvas' );
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext( '2d' );
ctx.finalize = (repetition = "repeat") => ctx.createPattern( canvas, repetition );
return ctx;
}
const canvas = document.getElementById("gc");
const ctx = canvas.getContext("2d");
const step = 50;
const offset = 30;
const patt_maker = patternMaker( step, step );
patt_maker.translate( 0.5, 0.5 );
patt_maker.moveTo( 0, 0 );
patt_maker.lineTo( offset, offset );
patt_maker.stroke();
const patt = patt_maker.finalize();
ctx.fillStyle = patt;
ctx.fillRect( 0, 0, canvas.width, canvas.height );
<canvas id="gc" width="500" height="500"></canvas>

JavaScript: One Canvas Multiple Textures

I'm trying to render multiple images on a single canvas, but seem to be overlooking something:
var canvas = document.createElement('canvas');
canvas.width = 4086;
canvas.height = 4086;
var ctx = canvas.getContext('2d');
var loadedImages = 0;
for (var j=0; j<4; j++) {
var url = 'https://via.placeholder.com/2048x2048';
var img = new Image();
img.onload = function(j) {
var w = 2048;
var h = 2048;
var x = (j % 2) * w;
var y = (Math.floor(j / 2)) * h;
console.log(img, x, y, w, h)
ctx.drawImage(img, x, y, w, h, x, y, w, h);
if (++loadedImages == 4) {
document.body.appendChild(canvas)
}
}.bind(null, j)
img.src = url;
}
canvas {
width: 500px;
}
Does anyone see what I'm missing? Any help others can offer would be greatly appreciated!
You're using the long-form API to draw the image, which is designed to select a portion of your source image to draw onto the canvas. You really only need the "short" version, because (I think) you want to draw the entire image four times.
When you use the long form, the code is requesting pixels that don't exist in the source image because it's only 2048x2048 pixels in size.

Sprite Animation clearRect alternative?

WHAT? I am attempting to use canvas and JavaScript to display an animation on top of a grid which also must be drawn using JavaScript. https://jsfiddle.net/cp1wqeeg/6/
PROBLEM! To remove the previous frames of the animation I have used clearRect(). This however breaks my grid which I do not want :(
JSFiddle: https://jsfiddle.net/cp1wqeeg/5
ctx.clearRect(50, 100, width, height);
QUESTION How can I remove the previous frames of my animation without breaking the grid behind my sprite?
The common action here is to clear all and redraw everything.
But it may become cumbersome if e.g in your case, your background doesn't change.
In this case, an simple solution, is to use offscreen canvases, that will act as layers.
First you draw you grid on this off-screen canvas in the init phase.
Then in your loop, you just draw your offscreen canvas on the main context, with the drawImage method.
var canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
fov = 300,
viewDist = 5,
w = canvas.width / 2,
h = canvas.height / 2,
// here we create an offscreen canvas for the grid only
gridCtx = canvas.cloneNode().getContext('2d'),
angle = 0,
i, p1, p2,
grid = 5;
function initGrid(){
/// create vertical lines on the off-screen canvas
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
i++;
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
}
gridCtx.stroke();
}
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180;
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca;
rz = y * sa;
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
initGrid();
var width = 200,
height = 200,
frames = 2,
currentFrame = 0,
imageSprite = new Image()
imageSprite.src = 'https://s27.postimg.org/eg1cjz6cz/sprite.png';
var drawSprite = function(){
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.drawImage(gridCtx.canvas, 0,0); // now draw our grid canvas
ctx.drawImage(imageSprite, 0, height * currentFrame, width, height, 50, 100, width, height);
if (currentFrame == frames) {
currentFrame = 0;
} else {
currentFrame++;
}
}
setInterval(drawSprite, 500);
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #c3c3c3;"></canvas>

Chroma keying with javascript & jQuery

Okay, we need your help! We (with our informatics class) are building a digital scratchmap! Like this:
(source: megagadgets.nl)
With your mouse you should be able to scratch out the places you've been to. Now we're stuck. We have a canvas and we draw the image of a world map. Then when the user clicks and drags a stroke gets add on top of the world map.
Now we want to convert the (green drawn) strokes to transparency so we can reveal the image behind it. (Just like scratching out the places you've been to and revealing the map behind it (in colour)).
This is our html:
<body>
<h1>Scratchmap</h1>
<hr>
<canvas id="ball" width="600px" height ="600px">
</canvas>
<canvas id="ball2" width="600px" height ="600px">
</canvas>
</body>
And this is our javascript:
// Set variables
var a_canvas = document.getElementById("ball");
var context = a_canvas.getContext("2d");
var a_canvas2 = document.getElementById("ball2");
var context2 = a_canvas2.getContext("2d");
var img = new Image();
img.onload = function () {
context.drawImage(img, img_x, img_y);
}
img.src = "worldmap.png"
var mouse_pos_x = [];
var mouse_pos_y = [];
var thickness = 0;
var arraycount = 0;
var mouse_down = false;
var mouse_skip = [];
function update() {}
document.body.onmousedown = function () {
mouse_down = true;
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
arraycount += 1;
}
document.body.onmouseup = function () {
if (mouse_down) {
mouse_down = false;
mouse_skip.push(arraycount);
}
}
document.body.onmousemove = function () {
if (mouse_down) {
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
context.drawImage(img, 0, 0);
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
context.lineWidth = 2.5;
context.strokeStyle = "#00FF00";
context.moveTo(mouse_pos_x[arraycount - 1], mouse_pos_y[arraycount - 1]);
context.lineTo(mouse_pos_x[arraycount], mouse_pos_y[arraycount]);
context.stroke();
arraycount += 1;
var imgdata = context.getImageData(0, 0, a_canvas.width, a_canvas.height);
var l = imgdata.data.length / 4;
for (var i = 0; i < l; i++) {
var r = imgdata.data[i * 4 + 0];
var g = imgdata.data[i * 4 + 1];
var b = imgdata.data[i * 4 + 2];
if (g < 255) {
imgdata.data[i * 4 + 3] = 0;
}
}
context2.putImageData(imgdata, 0, 0);
}
}
setInterval(update, 10);
Now when we remove the draw_image() the green color becomes yellow on the other canvas. But with the draw_image() nothing gets drawn on the second canvas.
What's going wrong? Or do you have a way to do this with other Javascript or not in javascript at all?
Any help would be appreciated!
Luud Janssen & Friends
You can do this with a slightly different approach:
Set the hidden image as CSS background
Draw the cover image on top using context
Change composite mode to destination-out
Anything now drawn will erase instead of draw revealing the (CSS set) image behind
Live demo
The key code (see demo linked above for details):
function start() {
/// draw top image - background image is already set with CSS
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
/// KEY: this will earse where next drawing is drawn
ctx.globalCompositeOperation = 'destination-out';
canvas.onmousedown = handleMouseDown;
canvas.onmousemove = handleMouseMove;
window.onmouseup = handleMouseUp;
}
Then it's just a matter of tracking the mouse position and draw any shape to erase that area, for example a circle:
function erase(x, y) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, pi2);
ctx.fill();
}
Random images for illustrative purposes

Simply canvas animation

I have a simple canvas animation: two rectangles move in two different directions. However, I feel this could be simplified more.
http://jsfiddle.net/tmyie/R5wx8/6/
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
x = 10,
y = 15,
a = 20,
b = 50;
function move() {
c.clearRect(0, 0, 500, 300);
c.fillRect(0, y, 5, 5),
c.fillRect(b, 5, 15, 15);
x++;
y++;
b++
if (y > canvas.height || x > canvas.width) {
y = 0;
x = 0;
}
}
setInterval(move, 100);
For example, what happens if I wanted to create another three shapes? At the moment, I'd have to create more variables for each coordinate:
x++;
y++;
b++
Is there a way I could turn each rectangle into its own object?
You can certainly turn them into objects, for example:
function Rect(x, y, w, h, dltX, dltY, color) {
var me = this;
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.deltaX = dltX || 0; /// and deltas can be optional
this.deltaY = dltY || 0;
this.color = color || '#000'; /// color is optional
this.update = function(ctx) {
me.x += me.deltaX;
me.y += me.deltaY;
ctx.fillStyle = me.color;
ctx.fillRect(me.x, me.y, me.width, me.height);
}
}
The deltaX and deltaY are how much you want to move the rectangle for each update. If you set these to for example 1 then x and y will be increased with 1 each time update() is called.
Using deltas makes it easy to create bounces (see demo below) by simply reversing the delta value (ie. delta = -delta) as well as things such as acceleration, variate speed, you can feed them through trigonometric functions to have the object move in a specific angle and so forth.
You can used fixed values if you desire but you will discover that deltas are beneficial in the long run (ref. comment: it's actually a very classic method used in for instance the first Pong games :-) ).
Online demo here
Now that we have defined the object we can simply create instances of it and store them in an array:
var rects = [
new Rect(10, 10, 100, 100, 1, -2),
new Rect(100, 1, 50, 50, 2, 1, '#f00'),
...
]
From here it's simply a matter of iterating the array to update each object:
function move() {
ctx.clearRect(0, 0, width, height);
for(var i = 0, r; r = rects[i]; i++) {
/// check any conditions here
r.update(ctx);
}
requestAnimationFrame(move);
}
requestAnimationFrame(move); /// start loop
Here's a slightly simpler version, though in the long term I'd recommend Ken's. In mine the rects are still just property bags, with no behavior on their own.
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
rects = [{x:0, y:15, w:5, h:5, vx:0, vy:1},
{x:50, y:5, w:15, h:15, vx:1, vy:0}];
function move() {
c.clearRect(0, 0, 500, 300);
for (var i=0; i < rects.length; i++) {
var rect = rects[i];
c.fillRect(rect.x, rect.y, rect.w, rect.h),
rect.x += rect.vx;
rect.y += rect.vy;
}
}
setInterval(move, 100);

Categories

Resources