Resizing Image Without Affecting Look of Original Canvas? - javascript

I have a canvas where a user draws. After tapping a button, I do a few things in a second canvas such as trimming away the white space and re-centering the drawing (so as to not affect the original canvas).
I also create a third canvas so I can resize the output to a certain size. My problem is that I don't want the original canvas where users draw to be affected. Right now everything works and my image is resized, but so is the original canvas. How to I leave the original canvas unaffected?
Here's my function:
//Get Canvas
c = document.getElementById('simple_sketch');
//Define Context
var ctx = c.getContext('2d');
//Create Copy of Canvas
var copyOfContext = document.createElement('canvas').getContext('2d');
//Get Pixels
var pixels = ctx.getImageData(0, 0, c.width, c.height);
//Get Length of Pixels
var lengthOfPixels = pixels.data.length;
//Define Placeholder Variables
var i;
var x;
var y;
var bound = {
top: null,
left: null,
right: null,
bottom: null
};
//Loop Through Pixels
for (i = 0; i < lengthOfPixels; i += 4) {
if (pixels.data[i+3] !== 0) {
x = (i / 4) % c.width;
y = ~~((i / 4) / c.width);
if (bound.top === null) {
bound.top = y;
}
if (bound.left === null) {
bound.left = x;
} else if (x < bound.left) {
bound.left = x;
}
if (bound.right === null) {
bound.right = x;
} else if (bound.right < x) {
bound.right = x;
}
if (bound.bottom === null) {
bound.bottom = y;
} else if (bound.bottom < y) {
bound.bottom = y;
}
}
}
//Calculate Trimmed Dimensions
var padding = 1;
var trimmedHeight = bound.bottom + padding - bound.top;
var trimmedWidth = bound.right + padding - bound.left;
//Get Longest Dimension (We Need a Square Image That Fits the Drawing)
var longestDimension = Math.max(trimmedHeight, trimmedWidth);
//Define Rect
var trimmedRect = ctx.getImageData(bound.left, bound.top, trimmedWidth, trimmedHeight);
//Define New Canvas Parameters
copyOfContext.canvas.width = longestDimension;
copyOfContext.canvas.height = longestDimension;
copyOfContext.putImageData(trimmedRect, (longestDimension - trimmedWidth)/2, (longestDimension - trimmedHeight)/2);
copyOfContext.globalCompositeOperation = "source-out";
copyOfContext.fillStyle = "#fff";
copyOfContext.fillRect(0, 0, longestDimension, longestDimension);
//Define Resized Context
var resizedContext = c.getContext('2d');
resizedContext.canvas.width = 32;
resizedContext.canvas.height = 32;
resizedContext.drawImage(copyOfContext.canvas, 0, 0, 32, 32);
//Get Cropped Image URL
var croppedImageURL = resizedContext.canvas.toDataURL("image/jpeg");
//Open Image in New Window
window.open(croppedImageURL, '_blank');

How to make a "spare" copy of an html5 canvas:
var theCopy=copyCanvas(originalCanvas);
function copyCanvas(originalCanvas){
var c=originalCanvas.cloneNode();
c.getContext('2d').drawImage(originalCanvas,0,0);
return(c);
}
Make a spare copy of the original canvas you don't want affected. Then after you've altered the original, but want the original contents back ...
// optionally clear the canvas before restoring the original content
originalCanvasContext.drawImage(theCopy,0,0);

Related

Cant draw same image on multiple canvases

I have three canvases adjacent to each other and an image which I want to draw on two of them. The code below draws the image twice on one canvas (as desired), but won't draw it on another canvas, as shown in the screenshot below.
Through debugging, I have established that the line c1.drawImage(img, x, y, 50, 50); runs (this can be seen in the console where "here" is output). This should draw the image onto the second canvas, however it doesn't.
JsFiddle: https://jsfiddle.net/3xnpys9m/1/
var area0 = document.getElementById("area-0");
var area1 = document.getElementById("area-1");
var area2 = document.getElementById("area-2");
var c0 = area0.getContext("2d");
var c1 = area1.getContext("2d");
var c2 = area2.getContext("2d");
// Set height and width of areas
area0.width = area1.width = area2.width = 150;
area0.height = area1.height = area2.height = 150;
var arr; // holds all positions
var img;
populate();
function populate() {
arr = [
[0, 0],
[40, 40],
[170, 0]
];
img = new Image();
img.onload = function() {
// for each position in array
for (var i = 0; i < arr.length; i++) {
var x = arr[i][0]; // x position
var y = arr[i][1]; // y position
draw(x, y);
}
}
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/SNice.svg/1200px-SNice.svg.png";
}
// Draw onto canvas
function draw(x, y) {
var area;
// Work out which area to draw in
if (x < area0.width + 1) {
area = 0;
} else if (x < (area0.width * 2) + 1) {
area = 1;
} else if (x < (area0.width * 3) + 1) {
area = 2;
}
// Draw onto correct area
if (area == 0) {
c0.drawImage(img, x, y, 50, 50);
} else if (area == 1) {
console.log("here");
c1.drawImage(img, x, y, 50, 50);
} else if (area == 2) {
c2.drawImage(img, x, y, 50, 50);
}
}
#canvases {
width: 470px;
}
<div id="canvases">
<canvas id="area-0"></canvas>
<canvas id="area-1"></canvas>
<canvas id="area-2"></canvas>
</div>
I thought the problem could be related to drawing the same image across multiple canvases, however removing the first two items from the array arr doesn't resolve the problem.
If you are going to draw you images and have your functions calculate their position in this manner then you must account for the canvas position relative to the window. You can use getBoundingClientRect() to get the coordinates and then subtract the x value from the x in drawImage(). Same for the y coordinate. This accounts for the body margin too.
var area0 = document.getElementById("area-0");
var area1 = document.getElementById("area-1");
var area2 = document.getElementById("area-2");
var c0 = area0.getContext("2d");
var c1 = area1.getContext("2d");
var c2 = area2.getContext("2d");
// Set height and width of areas
area0.width = area1.width = area2.width = 150;
area0.height = area1.height = area2.height = 150;
let a0Bnds = area0.getBoundingClientRect();
let a1Bnds = area1.getBoundingClientRect();
let a2Bnds = area2.getBoundingClientRect();
var arr; // holds all positions
var img;
populate();
function populate() {
arr = [
[0, 0],
[40, 40],
[170, 0]
];
img = new Image();
img.onload = function() {
// for each position in array
for (var i = 0; i < arr.length; i++) {
var x = arr[i][0]; // x position
var y = arr[i][1]; // y position
draw(x, y);
}
}
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/SNice.svg/1200px-SNice.svg.png";
}
// Draw onto canvas
function draw(x, y) {
var area;
// Work out which area to draw in
if (x < area0.width + 1) {
area = 0;
} else if (x < (area0.width * 2) + 1) {
area = 1;
} else if (x < (area0.width * 3) + 1) {
area = 2;
}
//console.log(a1Bnds.x)
// Draw onto correct area
if (area == 0) {
c0.drawImage(img, x - a0Bnds.x, y - a0Bnds.y, 50, 50);
} else if (area == 1) {
c1.drawImage(img, x - a1Bnds.x, y - a1Bnds.y, 50, 50);
} else if (area == 2) {
c2.drawImage(img, x - a2Bnds.x, y - a2Bnds.y, 50, 50);
}
}
#canvases {
width: 470px;
}
<div id="canvases">
<canvas id="area-0"></canvas>
<canvas id="area-1"></canvas>
<canvas id="area-2"></canvas>
</div>
You draw the third image at x coordinate 170, however, the canvas is only 150 wide.

how to use image as a canvas in javascript

I have a script which I'm using to draw rectangles on a canvas. The user can drag/reposition the drawn rectangles and also add new ones by double-clicking. Now I want to add an image on the background that is, I don't want to use a plain canvas but instead an image on which I can place my rectangles.
What I am trying to build is these rectangles correspond to particular regions of the images. Once this images and rectangles are loaded, if the regions and rectangles are not aligned, the user should be able to reposition them correctly.
Here is my code:
//Box object to hold data for all drawn rects
function Box() {
this.x = 0;
this.y = 0;
this.w = 1; // default width and height?
this.h = 1;
this.fill = '#444444';
}
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes.push(rect);
invalidate();
}
// holds all our rectangles
var boxes = [];
var canvas;
var ctx;
var WIDTH;
var HEIGHT;
var INTERVAL = 20; // how often, in milliseconds, we check to see if a redraw is needed
var isDrag = false;
var mx, my; // mouse coordinates
// when set to true, the canvas will redraw everything
// invalidate() just sets this to false right now
// we want to call invalidate() whenever we make a change
var canvasValid = false;
// The node (if any) being selected.
// If in the future we want to select multiple objects, this will get turned into an array
var mySel;
// The selection color and width. Right now we have a red selection with a small width
var mySelColor = '#CC0000';
var mySelWidth = 2;
// we use a fake canvas to draw individual shapes for selection testing
var ghostcanvas;
var gctx; // fake canvas context
// since we can drag from anywhere in a node
// instead of just its x/y corner, we need to save
// the offset of the mouse when we start dragging.
var offsetx, offsety;
// Padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function init() {
// canvas = fill_canvas();
canvas = document.getElementById('canvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () { return false; }
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make draw() fire every INTERVAL milliseconds
setInterval(draw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
// add custom initialization here:
// add an orange rectangle
addRect(200, 200, 200, 200, '#FFC02B');
// add a smaller blue rectangle
addRect(25, 90, 250, 150 , '#2BB8FF');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
function draw() {
if (canvasValid == false) {
clear(ctx);
// Add stuff you want drawn in the background all the time here
// draw all boxes
var l = boxes.length;
for (var i = 0; i < l; i++) {
drawshape(ctx, boxes[i], boxes[i].fill);
}
// draw selection
// right now this is just a stroke along the edge of the selected box
if (mySel != null) {
ctx.strokeStyle = mySelColor;
ctx.lineWidth = mySelWidth;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
// Add stuff you want drawn on top all the time here
canvasValid = true;
}
}
// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
function drawshape(context, shape, fill) {
context.fillStyle = fill;
// We can skip the drawing of elements that have moved off the screen:
if (shape.x > WIDTH || shape.y > HEIGHT) return;
if (shape.x + shape.w < 0 || shape.y + shape.h < 0) return;
context.fillRect(shape.x,shape.y,shape.w,shape.h);
}
// Happens when the mouse is moving inside the canvas
function myMove(e){
if (isDrag){
getMouse(e);
mySel.x = mx - offsetx;
mySel.y = my - offsety;
// something is changing position so we better invalidate the canvas!
invalidate();
}
}
// Happens when the mouse is clicked in the canvas
function myDown(e){
getMouse(e);
clear(gctx);
var l = boxes.length;
for (var i = l-1; i >= 0; i--) {
// draw shape onto ghost context
drawshape(gctx, boxes[i], 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
canvas.onmousemove = myMove;
invalidate();
clear(gctx);
return;
}
}
// havent returned means we have selected nothing
mySel = null;
// clear the ghost canvas for next time
clear(gctx);
// invalidate because we might need the selection border to disappear
invalidate();
}
function myUp(){
isDrag = false;
canvas.onmousemove = null;
}
// adds a new node
function myDblClick(e) {
getMouse(e);
// for this method width and height determine the starting X and Y, too.
// so I left them as vars in case someone wanted to make them args for something and copy this code
var width = 150;
var height = 100;
addRect(mx - (width / 2), my - (height / 2), width, height, '#77DD44');
}
function invalidate() {
canvasValid = false;
}
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}
// If you dont want to use <body onLoad='init()'>
// You could uncomment this init() reference and place the script reference inside the body tag
init();
<!DOCTYPE html>
<html>
<head>
<title>js</title>
</head>
<body>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</body>
</html>
The snippet below should work if an image is loaded in the html.
Changes in the code are two:
loading the image into a variable
drawing the image onto the canvas using ctx.drawImage(im, 0, 0)
//Box object to hold data for all drawn rects
function Box() {
this.x = 0;
this.y = 0;
this.w = 1; // default width and height?
this.h = 1;
this.fill = '#444444';
}
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes.push(rect);
invalidate();
}
//***************************
// This will load the image into the variable "im"
var im = document.getElementById("myImage");
//***************************
// holds all our rectangles
var boxes = [];
var canvas;
var ctx;
var WIDTH;
var HEIGHT;
var INTERVAL = 20; // how often, in milliseconds, we check to see if a redraw is needed
var isDrag = false;
var mx, my; // mouse coordinates
// when set to true, the canvas will redraw everything
// invalidate() just sets this to false right now
// we want to call invalidate() whenever we make a change
var canvasValid = false;
// The node (if any) being selected.
// If in the future we want to select multiple objects, this will get turned into an array
var mySel;
// The selection color and width. Right now we have a red selection with a small width
var mySelColor = '#CC0000';
var mySelWidth = 2;
// we use a fake canvas to draw individual shapes for selection testing
var ghostcanvas;
var gctx; // fake canvas context
// since we can drag from anywhere in a node
// instead of just its x/y corner, we need to save
// the offset of the mouse when we start dragging.
var offsetx, offsety;
// Padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function init() {
// canvas = fill_canvas();
canvas = document.getElementById('canvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () { return false; }
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make draw() fire every INTERVAL milliseconds
setInterval(draw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
// add custom initialization here:
// add an orange rectangle
addRect(200, 200, 200, 200, '#FFC02B');
// add a smaller blue rectangle
addRect(25, 90, 250, 150 , '#2BB8FF');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
function draw() {
if (canvasValid == false) {
clear(ctx);
// Add stuff you want drawn in the background all the time here
// draw all boxes
var l = boxes.length;
for (var i = 0; i < l; i++) {
drawshape(ctx, boxes[i], boxes[i].fill);
}
// draw selection
// right now this is just a stroke along the edge of the selected box
if (mySel != null) {
ctx.strokeStyle = mySelColor;
ctx.lineWidth = mySelWidth;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
// Add stuff you want drawn on top all the time here
//You might want to change the size of the image
//In that case use ctx.drawImage(im,0,0,width,height);
//***************************
ctx.drawImage(im,0,0);
//***************************
canvasValid = true;
}
}
// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
function drawshape(context, shape, fill) {
context.fillStyle = fill;
// We can skip the drawing of elements that have moved off the screen:
if (shape.x > WIDTH || shape.y > HEIGHT) return;
if (shape.x + shape.w < 0 || shape.y + shape.h < 0) return;
context.fillRect(shape.x,shape.y,shape.w,shape.h);
}
// Happens when the mouse is moving inside the canvas
function myMove(e){
if (isDrag){
getMouse(e);
mySel.x = mx - offsetx;
mySel.y = my - offsety;
// something is changing position so we better invalidate the canvas!
invalidate();
}
}
// Happens when the mouse is clicked in the canvas
function myDown(e){
getMouse(e);
clear(gctx);
var l = boxes.length;
for (var i = l-1; i >= 0; i--) {
// draw shape onto ghost context
drawshape(gctx, boxes[i], 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
canvas.onmousemove = myMove;
invalidate();
clear(gctx);
return;
}
}
// havent returned means we have selected nothing
mySel = null;
// clear the ghost canvas for next time
clear(gctx);
// invalidate because we might need the selection border to disappear
invalidate();
}
function myUp(){
isDrag = false;
canvas.onmousemove = null;
}
// adds a new node
function myDblClick(e) {
getMouse(e);
// for this method width and height determine the starting X and Y, too.
// so I left them as vars in case someone wanted to make them args for something and copy this code
var width = 150;
var height = 100;
addRect(mx - (width / 2), my - (height / 2), width, height, '#77DD44');
}
function invalidate() {
canvasValid = false;
}
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}
// If you dont want to use <body onLoad='init()'>
// You could uncomment this init() reference and place the script reference inside the body tag
init();
<!DOCTYPE html>
<html>
<head>
<title>js</title>
</head>
<body>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<!-- This image will be displayed on the canvas -->
<img id="myImage" src="image.jpg" style = "display: none">
</body>
</html>

HTML5 Canvas: Bouncing Balls with Image Overlay

I'm really struggling with a couple problems in the HTML5 canvas.
I've posted the project to GitHub pages (https://swedy13.github.io/) and added an image (the circles are in motion) so you can see the issue. Basically, if you scroll down you'll find several green circles bouncing around on the page. I'd like to replace those with my client logos.
I'm calling requestAnimation from three files based on different actions, all of which can be found in https://github.com/swedy13/swedy13.github.io/tree/master/assets/js
Filenames:
- filters.js (calls requestAnimation when you use the filters)
- main.js (on load and resize)
- portfolio.js (this is where the canvas code is)
Update: I've added the "portfolio.js" code below so the answer can be self-contained.
function runAnimation(width, height, type){
var canvas = document.getElementsByTagName('canvas')[0];
var c = canvas.getContext('2d');
// ---- DIMENSIONS ---- //
// Container
var x = width;
var y = height - 65;
canvas.width = x;
canvas.height = y;
var container = {x: 0 ,y: 0 ,width: x, height: y};
// Portrait Variables
var cPos = 200;
var cMargin = 70;
var cSpeed = 3;
var r = x*.075;
if (y > x && x >= 500) {
cPos = x * (x / y) - 150;
cMargin = 150;
}
// Landscape Variables
if (x > y) {
cPos = y * (y / x) - 50;
cMargin = 150;
cSpeed = 3;
r = x*.05;
}
// ---- CIRCLES ---- //
// Circles
var circles = [];
var img = new Image();
// Gets active post ids and count
var activeName = [];
var activeLogo = [];
var activePosts = $('.active').map(function() {
activeName.push($(this).text().replace(/\s+/g, '-').toLowerCase());
// Returns the image source
/*activeLogo.push($(this).find('img').prop('src'));*/
// Returns an image node
var elem = document.getElementsByClassName($(this).text().replace(/\s+/g, '-').toLowerCase())
activeLogo.push(elem[0].childNodes[0]);
});
// Populates circle data
for (var i = 0; i < $('.active').length; i++) {
circles.push({
id:activeName[i],
r:r,
color: 100,
/*image: activeLogo[i],*/
x:Math.random() * cPos + cMargin,
y:Math.random() * cPos + cMargin,
vx:Math.random() * cSpeed + .25,
vy:Math.random() * cSpeed + .25
});
}
// ---- DRAW ---- //
requestAnimationFrame(draw);
function draw(){
c.fillStyle = 'white';
c.fillRect(container.x, container.y, container.width, container.height);
for (var i = 0; i < circles.length; i++){
/*var img = new Image();
var path = circles[i].image;*/
/*var size = circles[i].r * 2;*/
/*img.src = circles[4].image;*/
var img = activeLogo[i];
img.onload = function (circles) {
/*c.drawImage(img, 0, 0, size, size);*/
var pattern = c.createPattern(this, "repeat");
c.fillStyle = pattern;
c.fill();
};
c.fillStyle = 'hsl(' + circles[i].color + ', 100%, 50%)';
c.beginPath();
c.arc(circles[i].x, circles[i].y, circles[i].r, 0, 2*Math.PI, false);
c.fill();
// If the circle size/position is greater than the canvas width, bounce x
if ((circles[i].x + circles[i].vx + circles[i].r > container.width) || (circles[i].x - circles[i].r + circles[i].vx < container.x)) {
circles[i].vx = -circles[i].vx;
}
// If the circle size/position is greater than the canvas width, bounce y
if ((circles[i].y + circles[i].vy + circles[i].r > container.height) || (circles[i].y - circles[i].r + circles[i].vy < container.y)){
circles[i].vy = -circles[i].vy;
}
// Generates circle motion by adding position and velocity each frame
circles[i].x += circles[i].vx;
circles[i].y += circles[i].vy;
}
requestAnimationFrame(draw);
}
}
The way it works right now is:
1. I have my portfolio content set to "display: none" (eventually it will be a pop-up when they click on one of the circles).
2. The canvas is getting the portfolio objects from the DOM, including the image that I can't get to work.
3. If I use the "onload()" function, I can get the images to show up and repeat in the background. But it's just a static background - the circles are moving above it and revealing the background. That isn't what I want.
So basically, I'm trying to figure out how to attach the background image to the circle (based on the circle ID).
----------------- UPDATE -----------------
I can now clip the image to a circle and get the circle to move in the background. But it isn't visible on the page (I can tell it's moving by console logging it's position). The only time I see anything is when the circle lines up with the images position, then it shows.
function runAnimation(width, height, type){
var canvas = document.getElementsByTagName('canvas')[0];
var c = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// Collects portfolio information from the DOM
var activeName = [];
var activeLogo = [];
$('.active').map(function() {
var text = $(this).text().replace(/\s+/g, '-').toLowerCase();
var elem = document.getElementsByClassName(text);
activeName.push(text);
activeLogo.push(elem[0].childNodes[0]);
});
var img = new Image();
img.onload = start;
var circles = [];
var cPos = 200;
var cMargin = 70;
var cSpeed = 3;
for (var i = 0; i < 1; i++) {
circles.push({
id: activeName[i],
img: activeLogo[i],
size: 50,
xPos: Math.random() * cPos + cMargin,
yPos: Math.random() * cPos + cMargin,
xVel: Math.random() * cSpeed + .25,
yVel: Math.random() * cSpeed + .25,
});
img.src = circles[i].img;
}
requestAnimationFrame(start);
function start(){
for (var i = 0; i < circles.length; i++) {
var circle = createImageInCircle(circles[i].img, circles[i].size, circles[i].xPos, circles[i].yPos);
c.drawImage(circle, circles[i].size, circles[i].size);
animateCircle(circles[i]);
}
requestAnimationFrame(start);
}
function createImageInCircle(img, radius, x, y){
var canvas2 = document.createElement('canvas');
var c2 = canvas2.getContext('2d');
canvas2.width = canvas2.height = radius*2;
c2.fillStyle = 'white';
c2.beginPath();
c2.arc(x, y, radius, 0, Math.PI*2);
c2.fill();
c2.globalCompositeOperation = 'source-atop';
c2.drawImage(img, 0, 0, 100, 100);
return(canvas2);
}
function animateCircle(circle) {
// If the circle size/position is greater than the canvas width, bounce x
if ((circle.xPos + circle.xVel + circle.size > canvas.width) || (circle.xPos - circle.size + circle.xVel < 0)) {
console.log('Bounce X');
circle.xVel = -circle.xVel;
}
// If the circle size/position is greater than the canvas width, bounce y
if ((circle.yPos + circle.yVel + circle.size > canvas.height) || (circle.yPos + circle.yVel - circle.size < 0)) {
console.log('Bounce Y');
circle.yVel = -circle.yVel;
}
// Generates circle motion by adding position and velocity each frame
circle.xPos += circle.xVel;
circle.yPos += circle.yVel;
}
}
I'm not sure if I'm animating the correct thing. I've tried animating canvas2, but that didn't make sense to me.
PS - Sorry for the GitHub formatting, not sure why it looks like that.
PPS - Apologies for any junk code I didn't clean up. I've tried a lot of stuff and probably lost track of some of the changes.
PPPS - And forgive me for not making the answer self-contained. I thought linking to GitHub would be more useful, but I've updated the question to contain all the necessary info. Thanks for the feedback.
To get you started...
Here's how to clip an image into a circle using compositing.
The example code creates a single canvas logo-ball that you can reuse for each of your bouncing balls.
var logoball1=dreateImageInCircle(logoImg1,50);
var logoball2=dreateImageInCircle(logoImg2,50);
Then you can draw each logo-ball onto your main canvas like this:
ctx.drawImage(logoball1,35,40);
ctx.drawImage(logoball2,100,75);
There are many examples here on Stackoverflow of how to animate the balls around the canvas so I leave that part to you.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/m%26m600x455.jpg";
function start(){
var copy=createImageInCircle(img,50);
ctx.drawImage(copy,20,75);
ctx.drawImage(copy,150,120);
ctx.drawImage(copy,280,75);
}
function createImageInCircle(img,radius){
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=c.height=radius*2;
cctx.beginPath();
cctx.arc(radius,radius,radius,0,Math.PI*2);
cctx.fill();
cctx.globalCompositeOperation='source-atop';
cctx.drawImage(img,radius-img.width/2,radius-img.height/2);
return(c);
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>

How to divide image in tiles?

I have to achieve the following task:
divides the image into tiles, computes the average color of each tile,
fetches a tile from the server for that color, and composites the
results into a photomosaic of the original image.
What would be the best strategy? the first solution coming to my mind is using canvas.
A simple way to get pixel data and finding the means of tiles. The code will need more checks for images that do not have dimensions that can be divided by the number of tiles.
var image = new Image();
image.src = ??? // the URL if the image is not from your domain you will have to move it to your server first
// wait for image to load
image.onload = function(){
// create a canvas
var canvas = document.createElement("canvas");
//set its size to match the image
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d"); // get the 2d interface
// draw the image on the canvas
ctx.drawImage(this,0,0);
// get the tile size
var tileSizeX = Math.floor(this.width / 10);
var tileSizeY = Math.floor(this.height / 10);
var x,y;
// array to hold tile colours
var tileColours = [];
// for each tile
for(y = 0; y < this.height; y += tileSizeY){
for(x = 0; x < this.width; x += tileSizeX){
// get the pixel data
var imgData = ctx.getImageData(x,y,tileSizeX,tileSizeY);
var r,g,b,ind;
var i = tileSizeY * tileSizeX; // get pixel count
ind = r = g = b = 0;
// for each pixel (rgba 8 bits each)
while(i > 0){
// sum the channels
r += imgData.data[ind++];
g += imgData.data[ind++];
b += imgData.data[ind++];
ind ++;
i --;
}
i = ind /4; // get the count again
// calculate channel means
r /= i;
g /= i;
b /= i;
//store the tile coords and colour
tileColours[tileColours.length] = {
rgb : [r,g,b],
x : x,
y : y,
}
}
// all done now fetch the images for the found tiles.
}
I created a solution for this (I am not getting the tile images from back end)
// first function call to create photomosaic
function photomosaic(image) {
// Dimensions of each tile
var tileWidth = TILE_WIDTH;
var tileHeight = TILE_HEIGHT;
//creating the canvas for photomosaic
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
canvas.height = image.height;
canvas.width = image.width;
var imageData = context.getImageData(0, 0, image.width, image.height);
var pixels = imageData.data;
// Number of mosaic tiles
var numTileRows = image.width / tileWidth;
var numTileCols = image.height / tileHeight;
//canvas copy of image
var imageCanvas = document.createElement('canvas');
var imageCanvasContext = canvas.getContext('2d');
imageCanvas.height = image.height;
imageCanvas.width = image.width;
imageCanvasContext.drawImage(image, 0, 0);
//function for finding the average color
function averageColor(row, column) {
var blockSize = 1, // we can set how many pixels to skip
data, width, height,
i = -4,
length,
rgb = {
r: 0,
g: 0,
b: 0
},
count = 0;
try {
data = imageCanvasContext.getImageData(column * TILE_WIDTH, row * TILE_HEIGHT, TILE_HEIGHT, TILE_WIDTH);
} catch (e) {
alert('Not happening this time!');
return rgb;
}
length = data.data.length;
while ((i += blockSize * 4) < length) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i + 1];
rgb.b += data.data[i + 2];
}
// ~~ used to floor values
rgb.r = ~~(rgb.r / count);
rgb.g = ~~(rgb.g / count);
rgb.b = ~~(rgb.b / count);
return rgb;
}
// Loop through each tile
for (var r = 0; r < numTileRows; r++) {
for (var c = 0; c < numTileCols; c++) {
// Set the pixel values for each tile
var rgb = averageColor(r, c)
var red = rgb.r;
var green = rgb.g;
var blue = rgb.b;
// Loop through each tile pixel
for (var tr = 0; tr < tileHeight; tr++) {
for (var tc = 0; tc < tileWidth; tc++) {
// Calculate the true position of the tile pixel
var trueRow = (r * tileHeight) + tr;
var trueCol = (c * tileWidth) + tc;
// Calculate the position of the current pixel in the array
var pos = (trueRow * (imageData.width * 4)) + (trueCol * 4);
// Assign the colour to each pixel
pixels[pos + 0] = red;
pixels[pos + 1] = green;
pixels[pos + 2] = blue;
pixels[pos + 3] = 255;
};
};
};
};
// Draw image data to the canvas
context.putImageData(imageData, 0, 0);
return canvas;
}
function create() {
var image = document.getElementById('image');
var canvas = photomosaic(image);
document.getElementById("output").appendChild(canvas);
};
DEMO:https://jsfiddle.net/gurinderiitr/sx735L5n/
Try using the JIMP javascript library to read the pixel color and use invert, normalize or similar property for modifying the image.
Have a look on the jimp library
https://github.com/oliver-moran/jimp

Pixelate Image Data Algorithm very slow

I had this pixelate algorithm in my tools, but when I came to apply it today to my drawing app, it's performance is seriously bad. I was wondering if you could help me with this.
This is the algo I had:
//apply pixalate algorithm
for(var x = 1; x < w; x += aOptions.blockSize)
{
for(var y = 1; y < h; y += aOptions.blockSize)
{
var pixel = sctx.getImageData(x, y, 1, 1);
dctx.fillStyle = "rgb("+pixel.data[0]+","+pixel.data[1]+","+pixel.data[2]+")";
dctx.fillRect(x, y, x + aOptions.blockSize - 1, y + aOptions.blockSize - 1);
}
}
I was wondering if you could help me speed it up, I'm not sure whats causing this perf hit.
(yes i know the imageSmoothingEnabled techqnique however its not giving me perfect control over the block size)
Fast Hardware Pixilation
GPU can do it..
Why not just use the GPU to do it with the drawImage call, its much quicker. Draw the source canvas smaller at the number of pixels you want in the pixilation and then draw it to the source scaled back up. You just have to make sure that you turn off image smoothing before hand or the pixelation effect will not work.
No transparent pixels
For a canvas that has no transparent pixels it is a simplier solution the following function will do that. To pixelate the canvas where sctx is the source and dctx is the destination and pixelCount is the number of pixel blocks in the destination x axis. And you get filter option as a bonus because the GPU is doing the hard works for you.
Please Note that you should check vendor prefix for 2D context imageSmoothingEnabled and add them for the browsers you intend to support.
// pixelate where
// sctx is source context
// dctx is destination context
// pixelCount is the number of pixel blocks across in the destination
// filter boolean if true then pixel blocks as bilinear interpolation of all pixels involved
// if false then pixel blocks are nearest neighbour of pixel in center of block
function pixelate(sctx, dctx, pixelCount, filter){
var sw = sctx.canvas.width;
var sh = sctx.canvas.height;
var dw = dctx.canvas.width;
var dh = dctx.canvas.height;
var downScale = pixelCount / sw; // get the scale reduction
var pixH = Math.floor(sh * downScale); // get pixel y axis count
// clear destination
dctx.clearRect(0, 0, dw, dh);
// set the filter mode
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = filter;
// scale image down;
dctx.drawImage(sctx.canvas, 0, 0, pixelCount, pixH);
// scale image back up
// IMPORTANT for this to work you must turn off smoothing
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = false;
dctx.drawImage(dctx.canvas, 0, 0, pixelCount, pixH, 0, 0, dw, dh);
// restore smoothing assuming it was on.
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = true;
//all done
}
Transparent pixels
If you have transparent pixels you will need a work canvas to hold the scaled down image. To do this add a workCanvas argument and return that same argument. It will create a canvas for you and resize it if needed but you should also keep a copy of it so you don't needlessly create a new one each time you pixilate
function pixelate(sctx, dctx, pixelCount, filter, workCanvas){
var sw = sctx.canvas.width;
var sh = sctx.canvas.height;
var dw = dctx.canvas.width;
var dh = dctx.canvas.height;
var downScale = pixelCount / sw; // get the scale reduction
var pixH = Math.floor(sh * downScale); // get pixel y axis count
// create a work canvas if needed
if(workCanvas === undefined){
workCanvas = document.createElement("canvas");
workCanvas.width = pixelCount ;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}else // resize if needed
if(workCanvas.width !== pixelCount || workCanvas.height !== pixH){
workCanvas.width = pixelCount ;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}
// clear the workcanvas
workCanvas.ctx.clearRect(0, 0, pixelCount, pixH);
// set the filter mode Note the prefix, and I have been told same goes for IE
workCanvas.ctx.mozImageSmoothingEnabled = workCanvas.ctx.imageSmoothingEnabled = filter;
// scale image down;
workCanvas.ctx.drawImage(sctx.canvas, 0, 0, pixelCount, pixH);
// clear the destination
dctx.clearRect(0,0,dw,dh);
// scale image back up
// IMPORTANT for this to work you must turn off smoothing
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = false;
dctx.drawImage(workCanvas, 0, 0, pixelCount, pixH, 0, 0, dw, dh);
// restore smoothing assuming it was on.
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = true;
//all done
return workCanvas; // Return the canvas reference so there is no need to recreate next time
}
To use the transparent version you need to hold the workCanvas referance or you will need to create a new one each time.
var pixelateWC; // create and leave undefined in the app global scope
// (does not have to be JS context global) just of your app
// so you dont loss it between renders.
Then in your main loop
pixelateWC = pixelate(sourceCtx,destContext,20,true, pixelateWC);
Thus the function will create it the first time, and then will use it over and over untill it needs to change its size or you delete it with
pixelateWC = undefined;
The demo
I have included a demo (as my original version had a bug) to make sure it all works. Shows the transparent version of the functio. Works well fullscreen 60fp I do the whole canvas not just the split part. Instructions in demo.
// adapted from QuickRunJS environment.
// simple mouse
var mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, buttonRaw : 0,
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];}
e.preventDefault();
}
mouse.start = function(element, blockContextMenu){
if(mouse.element !== undefined){ mouse.removeMouse();}
mouse.element = element;
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
if(blockContextMenu === true){
element.addEventListener("contextmenu", preventDefault, false);
mouse.contextMenuBlocked = true;
}
}
mouse.remove = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(n => { mouse.element.removeEventListener(n, mouseMove); } );
if(mouse.contextMenuBlocked === true){ mouse.element.removeEventListener("contextmenu", preventDefault);}
mouse.contextMenuBlocked = undefined;
mouse.element = undefined;
}
}
return mouse;
})();
// delete needed for my QuickRunJS environment
function removeCanvas(){
if(canvas !== undefined){
document.body.removeChild(canvas);
}
canvas = undefined;
canvasB = undefined;
canvasP = undefined;
}
// create onscreen, background, and pixelate canvas
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
canvasP = document.createElement("canvas");
canvasB = document.createElement("canvas");
}
function resizeCanvas(){
if(canvas === undefined){ createCanvas(); }
canvasB.width = canvasP.width = canvas.width = window.innerWidth;
canvasB.height = canvasP.height = canvas.height = window.innerHeight;
canvasB.ctx = canvasB.getContext("2d");
canvasP.ctx = canvasP.getContext("2d");
canvas.ctx = canvas.getContext("2d");
// lazy coder bug fix
joinPos = Math.floor(window.innerWidth/2);
}
function pixelate(sctx, dctx, pixelCount, filter, workCanvas){
var sw = sctx.canvas.width;
var sh = sctx.canvas.height;
var dw = dctx.canvas.width;
var dh = dctx.canvas.height;
var downScale = pixelCount / sw; // get the scale reduction
var pixH = Math.floor(sh * downScale); // get pixel y axis count
// create a work canvas if needed
if(workCanvas === undefined){
workCanvas = document.createElement("canvas");
workCanvas.width = pixelCount;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}else // resize if needed
if(workCanvas.width !== pixelCount || workCanvas.height !== pixH){
workCanvas.width = pixelCount;
workCanvas.height = pixH;
workCanvas.ctx = workCanvas.getContext("2d");
}
// clear the workcanvas
workCanvas.ctx.clearRect(0, 0, pixelCount, pixH);
// set the filter mode
workCanvas.ctx.mozImageSmoothingEnabled = workCanvas.ctx.imageSmoothingEnabled = filter;
// scale image down;
workCanvas.ctx.drawImage(sctx.canvas, 0, 0, pixelCount, pixH);
// clear the destination
dctx.clearRect(0,0,dw,dh);
// scale image back up
// IMPORTANT for this to work you must turn off smoothing
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = false;
dctx.drawImage(workCanvas, 0, 0, pixelCount, pixH, 0, 0, dw, dh);
// restore smoothing assuming it was on.
dctx.mozImageSmoothingEnabled = dctx.imageSmoothingEnabled = true;
//all done
return workCanvas; // Return the canvas reference so there is no need to recreate next time
}
var canvas,canvaP, canvasB;
// create and size canvas
resizeCanvas();
// start mouse listening to canvas
mouse.start(canvas,true); // flag that context needs to be blocked
// listen to resize
window.addEventListener("resize",resizeCanvas);
// get some colours
const NUM_COLOURS = 10;
var currentCol = 0;
var cols = (function(count){
for(var i = 0, a = []; i < count; i ++){
a.push("hsl("+Math.floor((i/count)*360)+",100%,50%)");
}
return a;
})(NUM_COLOURS);
var holdExit = 0; // To stop in QuickRunJS environment
var workCanvas;
var joinPos = Math.floor(canvas.width / 2);
var mouseOverJoin = false;
var dragJoin = false;
var drawing = false;
var filterChange = 0;
var filter = true;
ctx = canvas.ctx;
function update(time){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(canvasB, 0, 0, joinPos, canvas.height, 0 , 0, joinPos, canvas.height); // draw background
ctx.drawImage(canvasP,
joinPos, 0,
canvas.width - joinPos, canvas.height,
joinPos, 0,
canvas.width - joinPos, canvas.height
); // pixilation background
if(dragJoin){
if(!(mouse.buttonRaw & 1)){
dragJoin = false;
canvas.style.cursor = "default";
if(filterChange < 20){
filter = !filter;
}
}else{
joinPos = mouse.x;
}
filterChange += 1;
mouseOverJoin = true;
}else{
if(Math.abs(mouse.x - joinPos) < 4 && ! drawing){
if(mouse.buttonRaw & 1){
canvas.style.cursor = "none";
dragJoin = true;
filterChange = 0;
}else{
canvas.style.cursor = "e-resize";
}
mouseOverJoin = true;
}else{
canvas.style.cursor = "default";
mouseOverJoin = false;
if(mouse.buttonRaw & 1){
canvasB.ctx.fillStyle = cols[currentCol % NUM_COLOURS];
canvasB.ctx.beginPath();
canvasB.ctx.arc(mouse.x, mouse.y, 20, 0, Math.PI * 2);
canvasB.ctx.fill();
drawing = true
}else{
drawing = false;
}
}
ctx.fillStyle = cols[currentCol % NUM_COLOURS];
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 20, 0, Math.PI * 2);
ctx.fill();
}
if(mouse.buttonRaw & 4){ // setp cols on right button
currentCol += 1;
}
if(mouse.buttonRaw & 2){ // setp cols on right button
canvasB.ctx.clearRect(0, 0, canvas.width, canvas.height);
holdExit += 1;
}else{
holdExit = 0;
}
workCanvas = pixelate(canvasB.ctx, canvasP.ctx, 30, filter, workCanvas);
ctx.strokeStyle = "rgba(0,0,0,0.5)";
if(mouseOverJoin){
ctx.fillStyle = "rgba(0,255,0,0.5)";
ctx.fillRect(joinPos - 3, 0, 6, canvas.height);
ctx.fillRect(joinPos - 2, 0, 4, canvas.height);
ctx.fillRect(joinPos - 1, 0, 2, canvas.height);
}
ctx.strokeRect(joinPos - 3, 0, 6, canvas.height);
ctx.font = "18px arial";
ctx.textAlign = "left";
ctx.fillStyle = "black"
ctx.fillText("Normal canvas.", 10, 20)
ctx.textAlign = "right";
ctx.fillText("Pixelated canvas.", canvas.width - 10, 20);
ctx.textAlign = "center";
ctx.fillText("Click drag to move join.", joinPos, canvas.height - 62);
ctx.fillText("Click to change Filter : " + (filter ? "Bilinear" : "Nearest"), joinPos, canvas.height - 40);
ctx.fillText("Click drag to draw.", canvas.width / 2, 20);
ctx.fillText("Right click change colour.", canvas.width / 2, 42);
ctx.fillText("Middle click to clear.", canvas.width / 2, 64);
if(holdExit < 60){
requestAnimationFrame(update);
}else{
removeCanvas();
}
}
requestAnimationFrame(update);
You fetch an ImageData object for each pixel, then construct a colour string which has to be parsed again by the canvas object and then use a canvas fill routine, which has to go through all the canvas settings (transformation matrix, composition etc.)
You also seem to paint rectangles that are bigger than they need to be: The third and fourth parameters to fillRect are the width and height, not the x and y coordinated of the lower right point. An why do you start at pixel 1, not at zero?
It is usually much faster to operate on raw data for pixel manipulations. Fetch the whole image as image data, manipulate it and finally put it on the destination canvas:
var idata = sctx.getImageData(0, 0, w, h);
var data = idata.data;
var wmax = ((w / blockSize) | 0) * blockSize;
var wrest = w - wmax;
var hmax = ((h / blockSize) | 0) * blockSize;
var hrest = h - hmax;
var hh = blockSize;
for (var y = 0; y < h; y += blockSize) {
var ww = blockSize;
if (y == hmax) hh = hrest;
for (var x = 0; x < w; x += blockSize) {
var n = 4 * (w * y + x);
var r = data[n];
var g = data[n + 1];
var b = data[n + 2];
var a = data[n + 3];
if (x == wmax) ww = wrest;
for (var j = 0; j < hh; j++) {
var m = n + 4 * (w * j);
for (var i = 0; i < ww; i++) {
data[m++] = r;
data[m++] = g;
data[m++] = b;
data[m++] = a;
}
}
}
}
dctx.putImageData(idata, 0, 0);
In my browser, that's faster even with the two additonal inner loops.

Categories

Resources