Is there any jquery plugin or a javascript library to produce photo mosaics, ie creating an image made up of smaller images ? (cf: http://en.wikipedia.org/wiki/Photographic_mosaic)
I have created a JS solution for creating photomosaic using average colors of a tile.
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/
Related
Alright. I'm new to this, I'm not coder, just trying something for fun. And I'm confused.
I've found a tutorial about making a duotone image with canvas, and I'm so new to this that I can't figure out what I'm doing wrong. Maybe someone can help.
Here is my code. It displays the original image (demo_small.png), but doesn't show any of the effects on it. I guess that maybe I've to overwrite it after the last "return pixels", but I've not idea of what I'm doing so..
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
<script src="https://www.mattkandler.com/assets/application-9cbca3f8879431193adab436bd8e0cf7629ecf3752685f49f997ed4469f42826.js" type="text/javascript"></script>
</head>
<body>
<canvas id="idOfCanvasToDrawImageOn" width="img.width" height="img.height"></canvas>
<script>
//Getting the image pixels
var canvasId = 'idOfCanvasToDrawImageOn';
var imageUrl = 'demo_small.png';
var canvas = document.getElementById(canvasId);
var context = canvas.getContext('2d');
var img = new Image();
// img.crossOrigin = 'Anonymous';
img.onload = function() {
// Perform image scaling if desired size is given
var scale = 1;
context.canvas.width = img.width;
context.canvas.height = img.height;
context.scale(scale, scale);
// Draw image on canvas
context.drawImage(img, 0, 0);
// Perform filtering here
};
img.src = imageUrl;
//Then we'll need to grab the pixels from this newly created canvas image using the following function
Filters.getPixels = function(img) {
var c = this.getCanvas(img.width, img.height);
var ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, c.width, c.height);
};
//Converting to grayscale
Filters.grayscale = function(pixels) {
var d = pixels.data;
var max = 0;
var min = 255;
for (var i = 0; i < d.length; i += 4) {
// Fetch maximum and minimum pixel values
if (d[i] > max) {
max = d[i];
}
if (d[i] < min) {
min = d[i];
}
// Grayscale by averaging RGB values
var r = d[i];
var g = d[i + 1];
var b = d[i + 2];
var v = 0.3333 * r + 0.3333 * g + 0.3333 * b;
d[i] = d[i + 1] = d[i + 2] = v;
}
for (var i = 0; i < d.length; i += 4) {
// Normalize each pixel to scale 0-255
var v = (d[i] - min) * 255 / (max - min);
d[i] = d[i + 1] = d[i + 2] = v;
}
return pixels;
};
//Building a color gradient
Filters.gradientMap = function(tone1, tone2) {
var rgb1 = hexToRgb(tone1);
var rgb2 = hexToRgb(tone2);
var gradient = [];
for (var i = 0; i < (256 * 4); i += 4) {
gradient[i] = ((256 - (i / 4)) * rgb1.r + (i / 4) * rgb2.r) / 256;
gradient[i + 1] = ((256 - (i / 4)) * rgb1.g + (i / 4) * rgb2.g) / 256;
gradient[i + 2] = ((256 - (i / 4)) * rgb1.b + (i / 4) * rgb2.b) / 256;
gradient[i + 3] = 255;
}
return gradient;
};
//Applying the gradient
Filters.duotone = function(img, tone1, tone2) {
var pixels = this.getPixels(img);
pixels = Filters.grayscale(pixels);
var gradient = this.gradientMap(tone1, tone2);
var d = pixels.data;
for (var i = 0; i < d.length; i += 4) {
d[i] = gradient[d[i] * 4];
d[i + 1] = gradient[d[i + 1] * 4 + 1];
d[i + 2] = gradient[d[i + 2] * 4 + 2];
}
return pixels;
};
</script>
</body>
</html>
I have been trying to learn filters in javascript, i have been following
https://www.html5rocks.com/en/tutorials/canvas/imagefilters/
this tutorial.
I came across some of code i don't get, can some body help me understanding these codes.
Filters.convolute = function(pixels, weights, opaque) {
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side/2);
var src = pixels.data;
var sw = pixels.width;
var sh = pixels.height;
// pad output by the convolution matrix
var w = sw;
var h = sh;
var output = Filters.createImageData(w, h);
var dst = output.data;
// go through the destination image pixels
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = weights[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3] * wt;
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = a + alphaFac*(255-a);
}
}
return output;
};
what is side and halfSide and why 4 for nested loop is used for. i am stuck here like many days.
I do, like you the same thing, I am trying to implement convolution filters using Javascript - TypeScript.
The reason why is 4 is because we have r, g, b, a
where r = red
where g = green
where b = blue
where a = alpha
this image data is inside an array of type Uint8ClampedArray
you get this information with this way:
const width = canvas.width;
const height = canvas.height;
const imageData = ctx.getImageData(0, 0, width, height);
and then to get the real image data:
const pixels = imageData.data;
The pixel data is a type of Uint8ClampedArray and can be represented like this:
[r, g, b, a, r, g, b, a, r, g, b, a ]
and every 4 elements in the array you get the pixel index and every 1,5 times you get the kernel Center but this depends on the kernel size 3x3 or 9x9
const image = imageData.data
The only code is working for me is this.
init() {
const img = new Image();
const img2 = new Image();
img.src = '../../../assets/graffiti.jpg';
img2.src = '../../../assets/graffiti.jpg';
const canvas: HTMLCanvasElement = this.canvas1.nativeElement;
const canvas2: HTMLCanvasElement = this.canvas2.nativeElement;
const ctx: CanvasRenderingContext2D = canvas.getContext('2d');
const ctx2: CanvasRenderingContext2D = canvas2.getContext('2d');
this.onImgLoad(img, ctx, canvas.width, canvas.height);
this.input(img2, ctx2, canvas2.width, canvas2.height);
}
input(img, ctx: CanvasRenderingContext2D, width, height) {
img.onload = () => {
ctx.drawImage(img, 0, 0);
};
}
onImgLoad(img, ctx: CanvasRenderingContext2D, width, height) {
img.onload = () => {
ctx.drawImage(img, 0, 0);
const kernelArr = new Kernel([
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
]);
const kernel = [
0, 1, 0,
0, 1, 0,
0, 1, 0
];
console.log(kernel);
const newImg = new Filter2D(ctx, width, height);
// const imgData = newImg.inverse(width, height); // applys inverse filter
const imgData = newImg.applyKernel(kernel);
ctx.putImageData(imgData, 0, 0);
};
}
class Filter2D {
width: number;
height: number;
ctx: CanvasRenderingContext2D;
imgData: ImageData;
constructor(ctx: CanvasRenderingContext2D, width: number, height: number) {
this.width = width;
this.height = height;
this.ctx = ctx;
this.imgData = ctx.getImageData(0, 0, width, height);
console.log(this.imgData);
}
grey(width: number, height: number): ImageData {
return this.imgData;
}
inverse(width: number, height: number): ImageData {
console.log('Width: ', width);
console.log('Height: ', height);
const pixels = this.imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255 - pixels[i]; // red
pixels[i + 1] = 255 - pixels[i + 1]; // green
pixels[i + 2] = 255 - pixels[i + 2]; // blue
}
return this.imgData;
}
applyKernel(kernel: any[]): ImageData {
const k1: number[] = [
1, 0, -1,
2, 0, -2,
1, 0, -1
];
const k2: number[] = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
kernel = k2;
const dim = Math.sqrt(kernel.length);
const pad = Math.floor(dim / 2);
const pixels: Uint8ClampedArray = this.imgData.data;
const width: number = this.imgData.width;
const height: number = this.imgData.height;
console.log(this.imgData);
console.log('applyKernelMethod start');
console.log('Width: ', width);
console.log('Height: ', height);
console.log('kernel: ', kernel);
console.log('dim: ', dim); // 3
console.log('pad: ', pad); // 1
console.log('dim % 2: ', dim % 2); // 1
console.log('pixels: ', pixels);
if (dim % 2 !== 1) {
console.log('Invalid kernel dimension');
}
let pix, i, r, g, b;
const w = width;
const h = height;
const cw = w + pad * 2; // add padding
const ch = h + pad * 2;
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
r = 0;
g = 0;
b = 0;
for (let kx = -pad; kx <= pad; kx++) {
for (let ky = -pad; ky <= pad; ky++) {
i = (ky + pad) * dim + (kx + pad); // kernel index
pix = 4 * ((row + ky) * cw + (col + kx)); // image index
r += pixels[pix++] * kernel[i];
g += pixels[pix++] * kernel[i];
b += pixels[pix ] * kernel[i];
}
}
pix = 4 * ((row - pad) * w + (col - pad)); // destination index
pixels[pix++] = r;
pixels[pix++] = g;
pixels[pix++] = b;
pixels[pix ] = 255; // we want opaque image
}
}
console.log(pixels);
return this.imgData;
}
}
I need to create a mosaic by replacing the pixel of an image on input with images for each colors. The images will be provided by me for some of the most important colors. Obviously it shall be rounded up to the nearest color, because i can't use a picture for every rgb color.
I found this code that i think it could be a good start but i don't know how replace a tile with an image.
var TILE_WIDTH=10;
var TILE_HEIGHT=10;
document.getElementById("input").onchange = function () {
var reader = new FileReader();
reader.onload = function (e) {
// get loaded data and render thumbnail.
document.getElementById("image").src = e.target.result;
};
// read the image file as a data URL.
reader.readAsDataURL(this.files[0]);
};
// 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);
};
#output, .container {
text-align: center;
}
.inputDiv {
margin: 20px 0px;
}
<div class="container">
<img id="image"/>
<div class="inputDiv">
<input id="input" type="file" accept="image/*">
<button onclick="create()">create</button>
</div>
<div id="output"></div>
</div>
code by gurinderiitr
I have a canvas where I use "fillText" with a string, saying for example "stackoverflow". Then I read the imagedata of the canvas in order to pick out each pixel of that text.
I want to pick the following from the pixel: x position, y position and its color. Then I would like to loop over that array with those pixels so I can draw back the text pixel by pixel so I have full control of each pixel, and can for example animate them.
However, I dont get it as smooth as I want. Look at my attach image, and you see the difference between the top text and then the text I've plotted out using fillRect for each pixel. Any help on how to make the new text look like the "fillText" text does?
Thanks
UPDATE: Added my code
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var data32 = new Uint32Array(_ctx.getImageData(0, 0, _width, _height).data.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillRect(x, y, 1, 1);
_ctx.fillStyle = 'green';
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
}
}
Here is an update to your code that reuses the alpha component of each pixel. There will still be some detail lost because we do not keep the antialiasing of the pixels (which in effect alters the actual color printed), but for this example the alpha is enough.
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.imageSmoothingEnabled= false;
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var pixels = _ctx.getImageData(0, 0, _width, _height).data;
var data32 = new Uint32Array(pixels.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
a: pixels[i*4 + 3] / 255
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillStyle = `rgba(0,128,0,${particle.alpha})`;
_ctx.fillRect(x, y, 1, 1);
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
this.alpha = pos.a;
}
}
<canvas id="textCanvas"></canvas>
I am making a map that renders position of game objects (Project Zomboid zombies):
As user zooms out, single dots are no longer useful. Instead, I'd like to render distribution of zombies on an area using red color gradient. I tried to loop over all zombies for every rendered pixel and color it reciprocally to the sum of squared distances to the zombies. The result:
That's way too blurry. Also the results are more influenced by the zombies that are AWAY from the points - I need to influence them more by the zombies that are CLOSE. So what this is is just math. Here's the code I used:
var h = canvas.height;
var w = canvas.width;
// To loop over more than 1 pixel (performance)
var tileSize = 10;
var halfRadius = Math.floor(tileSize/2);
var time = performance.now();
// "Squared" because we didnt unsquare it
function distanceSquared(A, B) {
return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}
// Loop for every x,y pixel (or region of pixels)
for(var y=0; y<h; y+=tileSize) {
for(var x=0; x<w; x+=tileSize) {
// Time security - stop rendering after 1 second
if(performance.now()-time>1000) {
x=w;y=h;break;
}
// Convert relative canvas offset to absolute point on the map
var point = canvasPixeltoImagePixel(x, y);
// For every zombie add sqrt(distance from this point to zombie)
var distancesRoot = 0;
// Loop over the zombies
var zombieCoords;
for(var i=0; i<zombies_length; i++) {
// Get single zombie coordinates as {x:0, y:0}
if((coords=zombies[i].pixel)==null)
coords = zombies[i].pixel = tileToPixel(zombies[i].coordinates[0], zombies[i].coordinates[1], drawer);
// square root is a) slow and b) probably not what I want anyway
var dist = distanceSquared(coords, point);
distancesRoot+=dist;
}
// The higher the sum of distances is, the more intensive should the color be
var style = 'rgba(255,0,0,'+300000000/distancesRoot+')';
// Kill the console immediatelly
//console.log(style);
// Maybe we should sample and cache the transparency styles since there's limited ammount of colors?
ctx.fillStyle = style;
ctx.fillRect(x-halfRadius,y-halfRadius,tileSize,tileSize);
}
}
I'm pretty fine with theoretical explanation how to do it, though if you make simple canvas example with some points, what would be awesome.
This is an example of a heat map. It's basically gradient orbs over points and then ramping the opacity through a heat ramp. The more orbs cluster together the more solid the color which can be shown as an amplified region with the proper ramp.
update
I cleaned up the variables a bit and put the zeeks in an animation loop. There's an fps counter to see how it's performing. The gradient circles can be expensive. We could probably do bigger worlds if we downscale the heat map. It won't be as smooth looking but will compute a lot faster.
update 2
The heat map now has an adjustable scale and as predicted we get an increase in fps.
if (typeof app === "undefined") {
var app = {};
}
app.zeeks = 200;
app.w = 600;
app.h = 400;
app.circleSize = 50;
app.scale = 0.25;
init();
function init() {
app.can = document.getElementById('can');
app.ctx = can.getContext('2d');
app.can.height = app.h;
app.can.width = app.w;
app.radius = Math.floor(app.circleSize / 2);
app.z = genZ(app.zeeks, app.w, app.h);
app.flip = false;
// Make temporary layer once.
app.layer = document.createElement('canvas');
app.layerCtx = app.layer.getContext('2d');
app.layer.width = Math.floor(app.w * app.scale);
app.layer.height = Math.floor(app.h * app.scale);
// Make the gradient canvas once.
var sCircle = Math.floor(app.circleSize * app.scale);
app.radius = Math.floor(sCircle / 2);
app.gCan = genGradientCircle(sCircle);
app.ramp = genRamp();
// fps counter
app.frames = 0;
app.fps = "- fps";
app.fpsInterval = setInterval(calcFps, 1000);
// start animation
ani();
flicker();
}
function calcFps() {
app.fps = app.frames + " fps";
app.frames = 0;
}
// animation loop
function ani() {
app.frames++;
var ctx = app.ctx;
var w = app.w;
var h = app.h;
moveZ();
//ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#006600";
ctx.fillRect(0, 0, w, h);
if (app.flip) {
drawZ2();
drawZ();
} else {
drawZ2();
}
ctx.fillStyle = "#FFFF00";
ctx.fillText(app.fps, 10, 10);
requestAnimationFrame(ani);
}
function flicker() {
app.flip = !app.flip;
if (app.flip) {
setTimeout(flicker, 500);
} else {
setTimeout(flicker, 5000);
}
}
function genGradientCircle(size) {
// gradient image
var gCan = document.createElement('canvas');
gCan.width = gCan.height = size;
var gCtx = gCan.getContext('2d');
var radius = Math.floor(size / 2);
var grad = gCtx.createRadialGradient(radius, radius, radius, radius, radius, 0);
grad.addColorStop(1, "rgba(255,255,255,.65)");
grad.addColorStop(0, "rgba(255,255,255,0)");
gCtx.fillStyle = grad;
gCtx.fillRect(0, 0, gCan.width, gCan.height);
return gCan;
}
function genRamp() {
// Create heat gradient
var heat = document.createElement('canvas');
var hCtx = heat.getContext('2d');
heat.width = 256;
heat.height = 5;
var linGrad = hCtx.createLinearGradient(0, 0, heat.width, heat.height);
linGrad.addColorStop(1, "rgba(255,0,0,.75)");
linGrad.addColorStop(0.5, "rgba(255,255,0,.03)");
linGrad.addColorStop(0, "rgba(255,255,0,0)");
hCtx.fillStyle = linGrad;
hCtx.fillRect(0, 0, heat.width, heat.height);
// create ramp from gradient
var ramp = [];
var imageData = hCtx.getImageData(0, 0, heat.width, 1);
var d = imageData.data;
for (var x = 0; x < heat.width; x++) {
var i = x * 4;
ramp[x] = [d[i], d[i + 1], d[i + 2], d[i + 3]];
}
return ramp;
}
function genZ(n, w, h) {
var a = [];
for (var i = 0; i < n; i++) {
a[i] = [
Math.floor(Math.random() * w),
Math.floor(Math.random() * h),
Math.floor(Math.random() * 3) - 1,
Math.floor(Math.random() * 3) - 1
];
}
return a;
}
function moveZ() {
var w = app.w
var h = app.h;
var z = app.z;
for (var i = 0; i < z.length; i++) {
var s = z[i];
s[0] += s[2];
s[1] += s[3];
if (s[0] > w || s[0] < 0) s[2] *= -1;
if (s[1] > w || s[1] < 0) s[3] *= -1;
}
}
function drawZ() {
var ctx = app.ctx;
var z = app.z;
ctx.fillStyle = "#FFFF00";
for (var i = 0; i < z.length; i++) {
ctx.fillRect(z[i][0] - 2, z[i][1] - 2, 4, 4);
}
}
function drawZ2() {
var ctx = app.ctx;
var layer = app.layer;
var layerCtx = app.layerCtx;
var gCan = app.gCan;
var z = app.z;
var radius = app.radius;
// render gradients at coords onto layer
for (var i = 0; i < z.length; i++) {
var x = Math.floor((z[i][0] * app.scale) - radius);
var y = Math.floor((z[i][1] * app.scale) - radius);
layerCtx.drawImage(gCan, x, y);
}
// adjust layer for heat ramp
var ramp = app.ramp;
// apply ramp to layer
var imageData = layerCtx.getImageData(0, 0, layer.width, layer.height);
d = imageData.data;
for (var i = 0; i < d.length; i += 4) {
if (d[i + 3] != 0) {
var c = ramp[d[i + 3]];
d[i] = c[0];
d[i + 1] = c[1];
d[i + 2] = c[2];
d[i + 3] = c[3];
}
}
layerCtx.putImageData(imageData, 0, 0);
// draw layer on world
ctx.drawImage(layer, 0, 0, layer.width, layer.height, 0, 0, app.w, app.h);
}
<canvas id="can" width="600" height="400"></canvas>