Replace average color pixel with images - javascript

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

Related

Duotone image in Canvas

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>

JavaScript - Generate random pixels on HTML5 canvas

I want to create a random generated image (random colors), like this one. But, I want to do it in javascript, but for some reason I am getting black screen.
Here is my code:
var g=document . createElement( 'canvas').getContext('2d');
g.canvas.width=g.canvas.height = 800;
g.imgd = g.getImageData(0, 0, 800, 800);
g.data = g.imgd.data;
g.data.forEach((_, index) => (index & 3) < 3 && (g.data[index] = Math.random()));
g.putImageData(g.imgd, 0, 0);
document.body.appendChild(g.canvas);;;
And i am getting black screen, and on some websites it is white screen. So what is what not working in my script? My english is not very good, but can someone explain what is wrong, my code dont'esnt working.
I also tried different dimensions of canvas and I dont see any errors so what is wrong?
You are using Math.random() which generates floats from 0 to 1 without including 1. Since you're applying zeroes to the color components (the data from getImageData().data), you get the color black (rgb(0, 0, 0)).
Here's a more readable solution:
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 800;
var ctx = canvas.getContext('2d');
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
for (var i = 0; i < imgData.data.length; i += 4) {
imgData.data[i] = randomInt(0, 255); // red
imgData.data[i+1] = randomInt(0, 255); // green
imgData.data[i+2] = randomInt(0, 255); // blue
imgData.data[i+3] = 255; // alpha
}
ctx.putImageData(imgData, 0, 0);
document.body.appendChild(canvas);
Math.random() returns a floating point number, not within the full range of 0-255. You can alternatively use .fillStyle() and set the color to a random hex color.
function pixels(width = 100, height = 100, size = 1, canvas) {
var canvas = canvas || document.createElement("canvas");
var ctx = canvas.getContext("2d");
var total = [];
canvas.width = width;
canvas.height = height;
function random() {
return "XXXXXX".replace(/X/g, function() {
var seed = "a0b1c2d3e4f56789";
return seed.charAt(Math.floor(Math.random() * seed.length))
})
};
for (var x = 0; x <= width; x += size) {
total.push(x)
};
total.forEach(function(value, index) {
for (var i = 0; i <= height; i++) {
ctx.fillStyle = "#" + random();
ctx.fillRect(value, total[i], size, size);
}
});
document.body.appendChild(canvas);
return ctx;
};
var c = pixels(window.innerWidth - 20, window.innerHeight - 20);

Detect image color via file, and change the background color

The code is simple:
I edit the link of the image on the jsfiddle and then the color of the background changes to the color of the image.
Now I want to upload the jsfiddle to my localhost but the problem i need debugging
the page if i want change the image url.
I test it and its just not work,
The image collapses but the background color does not.
What I need is the option to upload the image through file input,
So the background color will change on image load or image change.
onload() / change Or something like that.
jsfiddle:
jsfiddle.net/xLF38/2735/
<img id="i" src="data:image/png;base64,....." />
var rgb = getAverageRGB(document.getElementById('i'));
document.body.style.backgroundColor = 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
function getAverageRGB(imgEl) {
var blockSize = 5, // only visit every 5 pixels
defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0;
if (!context) {
return defaultRGB;
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
} catch(e) {
/* security error, img on diff domain */alert('x');
return defaultRGB;
}
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;
}
Run this:
$(function(){
$('#i').on('load change',function(){
var rgb = getAverageRGB(this);
$('body').css('backgroundColor',"rgb("+rgb.r+","+rgb.g+","+rgb.b+")");
});
$('#upload').on('change',function(){
var $file = this.files[0];
if($file!=null){
var $src = URL.createObjectURL($file);
$('#i').attr('src',$src);
}
});
});
function getAverageRGB(imgEl) {
var blockSize = 5, // only visit every 5 pixels
defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0;
if (!context){return defaultRGB;}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
}catch(e) {
/* security error, img on diff domain */
alert('x');
return defaultRGB;
}
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;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<img id="i" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALkAAABQCAYAAABF5tQWAAAENUlEQVR4nO2UWY7cMAxEff9LJz89gGGYEpeiKNFFoJCxW+JSfM51Xdc/iuqiZ/ze1zdGUSgRcqq9CDnVXoScai9CTrUXIafai5A31MtCPy1C3lCEXPaDkDcRIZf9IORNRMhlPwh5ExFy2Q9C3kSEXPaDkDcRIZf9IORNRMhlPwh5ExFy2Q9C3kSEXPaDkDcRIZf9IORNRMhlPwh5ExFy2Q9C3kSEXPaDkDcRIZf9IOQHS4rqvqReK735vY8Za4lqwztpd3+r+iqHfIeoXj5ymYOllvdW1Y/gR10DX44sL3cBvLr+o49aU6pN6Bae+dG+7rTflpB7AUBBhOgzEtG60Vl22//vfT2YmUNqF/B2NgqB9+4sRmcQuT0zVu994nl9Y5lDaheojaz7VhC9+RH9PfNY97Ny/xch90PqDRSE2fkRgGviM5AjjfDmWBkIH6y5EdBZe3me99Y9GnKvKSshz/oAPF5kQ474QKScmvdZTJVAPjN49PvIlFFEPjhrr9Y89+doTe2c9w9ndjcyG3JHx0OuGXR2PmqgJlAQ3PPd/9XUtPZn6dlTZ1ZbGy0gtww5ezf6fXbO8yHN8mSFtRYCcm9fz7ufhNxiyAzet7MRAzV5oh+xNaz1LF6/PWvnQAKIyLs15DPYtAZF4Xx+MNE8COA9sHgh9/SlrbsS8GMgjxjlNfB+DrmEjH5R96z9eeGO9Pw5yGdGvf2tMVIbqN5ndb0+RPxDzJjRMyGfmLXL8q2RAUz2jJ6aKznZEnJrZC4/uhDPLKshr9gXITca5jUzcyHeOA3yrDkJ+cMor5FZy7jftQYa8oz5ooAjah8Nucccj4nZS7jn0NZA9F0xozX+7hJyw2K057ywIGfL7tsSFYAjezgWco2J2ZBH+x2B/Tw/+jsTMNRuZv7Pnj8NubT8N4Osi9SG11RNrkhtRCB2E+3rSMi1g1mG1BhkMdIbUdg8PqE+MGveBMhef2sLuWVQ7T1UoD6IUU5t7izPqyBf1YdQP7+I13TtPUSshgmRA5kHtfvqnoRatZCPBn6eGd2JBno+RK6I/5baI2A9kGfNtg3kHuC05o3ueCNzrll+ZG9ZO/LmHdVYPdcWkEsGjMxa8XF58s7O7wK6ZR5PzvaQR0HXmLVygdZ+NXe7QY7OdwTkaNgtZkXvZy7w7Syqz6y9nAJ4GeRZoHtrZvWe6dNJe1k9wzaQe4DZwbDVkEu1TwP6LT4DOQr0Hfv9amg8qt7XcsgJUK942+no+RjIGedF5k7fcuz0HychD0TVIlfBcaI3Qi/1hsxiF7Mq+9lh7t13BoV8VZN/z/ffis36hE4DW7G3+sakZncxqtqLHTwg5M2009J20QmAS31e1U1R52hHqEc9EnKqpQg51V6EnGovQk61FyGn2ouQU+1FyKn2IuRUexFyqr0IOdVehJxqL0JOtRchpz6p/04AcdQFWWI+AAAAAElFTkSuQmCC" width="100" height="75"/>
<br>
<input type="file" id="upload">
What you want to do is set up a file upload field and set an event listener on it which will trigger a function to get the file via the File API and get its data uri, update the image element src, then call your function when the image loads.
HTML:
<img id="i" src="">
<input type="file" id="upload">
Listener:
var $upload = document.getElementById('upload');
$upload.addEventListener('change', updateImage);
Update image function:
// Will get data url via File API, update image src, then pass image element to setBackground
function updateImage(evt) {
var $image = document.getElementById('i');
var files = evt.target.files;
var file = files[0];
var reader = new FileReader();
reader.onload = function (e) {
$image.src = e.target.result;
};
$image.onload = function(e) {
setBackground($image);
};
reader.readAsDataURL(file);
}
Set background (putting some of your initial code into a function):
// Will pass image element to getAverageRGB then set background equal to result
function setBackground($image) {
var rgb = getAverageRGB($image);
document.body.style.backgroundColor = 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
}
Get RGB (your function):
function getAverageRGB(imgEl) {
var blockSize = 5, // only visit every 5 pixels
defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0;
if (!context) {
return defaultRGB;
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
} catch(e) {
/* security error, img on diff domain */
alert('x');
return defaultRGB;
}
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;
}
Fiddle: http://jsfiddle.net/2mw6r8Ls/
You have a bug, and your code could be a little more readable. You start i off as -4, rather than -4 * blockSize. However, I believe it'd be simpler if you started from zero and used a for loop. Also, the defaultRBG value is redundant. You can return rgb since they're the same before you loop through the pixels. Even more confusing is your use of ~~. Your code should be self-documenting. If you need to write a comment, ask yourself if there's a more readable way to write the code.
If you want to upload an image, you need an input[type="file"] element, and attach some handlers and logic for loading and reading the image. The MDN article goes through a thorough example. I'd recommend after getting the object URL and setting the img.src, call the function below in a load event listener, get the rgb object and change the body's background color.
function getAverageRGB(imgEl) {
const BLOCK_SIZE = 5; // only visit every 5 pixels
const rgb = {r: 0, g: 0, b: 0};
const canvas = document.createElement('canvas'),
const context = canvas.getContext && canvas.getContext('2d'),
let data, width, height, length;
let count = 0;
if (!context) {
return rgb;
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height).data;
} catch(e) {
// security error, img on diff domain
return rgb;
}
length = data.length;
for (let i = 0; i < length; i += blockSize * 4) {
rgb.r += data[i + 0];
rgb.g += data[i + 1];
rgb.b += data[i + 2];
count++;
}
rgb.r = Math.floor(rgb.r / count);
rgb.g = Math.floor(rgb.g / count);
rgb.b = Math.floor(rgb.b / count);
return rgb;
}

Create color gradient describing discrete distribution of points

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>

photo mosaics in javascript

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/

Categories