Resizing a canvas image without blurring it - javascript

I have a small image, which I am rendering on a canvas, like this:
ctx.drawImage(img, 0, 0, img.width*2, img.height*2);
I would like this to show a sharp upsized image (4 identical pixels for each image pixel). However, this code (in Chrome 29 on Mac) makes a blurry image. In Photoshop terms, it looks like it's using "Bicubic" resampling, instead of "Nearest Neighbour".
In a situation where it would be useful (eg. a retro game), Is it possible to produce a sharp upsized image, or do I need to have a seperate image file for each size of the image on the server?

Simply turn off canvas' anti-aliasing for images - unfortunately this property is still vendor prefixed so here are the variations:
context.webkitImageSmoothingEnabled = false;
context.mozImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
then draw the image.
Optionally for older versions and browsers which hasn't implemented this yet, you can use CSS instead:
canvas {
image-rendering: optimizeSpeed; // Older versions of FF
image-rendering: -moz-crisp-edges; // FF 6.0+
image-rendering: -webkit-optimize-contrast; // Webkit (non standard naming)
image-rendering: -o-crisp-edges; // OS X & Windows Opera (12.02+)
image-rendering: crisp-edges; // Possible future browsers.
-ms-interpolation-mode: nearest-neighbor; // IE (non standard naming)
}
ONLINE TEST HERE

Check this fiddle: http://jsfiddle.net/yPFjg/
It loads the image into a canvas, then creates a resized copy and uses that as sprite.
With few modifications, you can implement an image loader that resizes images on the fly.
var ctx = document.getElementById('canvas1').getContext('2d');
var img = new Image();
var original = document.createElement("canvas");
var scaled = document.createElement("canvas");
img.onload = function() {
var oc = original.getContext('2d');
var sc = scaled.getContext('2d');
oc.canvas.width = oc.canvas.height = 16;
sc.canvas.width = sc.canvas.height = 32;
oc.drawImage(this, 0, 0);
var od = oc.getImageData(0,0,16,16);
var sd = sc.getImageData(0,0,32,32);
for (var x=0; x<32; x++) {
for (var y=0; y<32; y++) {
for (var c=0; c<4; c++) {
// you can improve these calculations, I let them so for clarity
sd.data[(y*32+x)*4+c] = od.data[((y>>1)*16+(x>>1))*4+c];
}
}
}
sc.putImageData(sd, 0, 0);
ctx.drawImage(scaled, 0, 0);
}
img.src = document.getElementById('sprite').src;
Some notes about getImageData: it returns an object with an array. The array has a height*width*4 size. The color components are stored in RGBA order (red, green, blue, alpha, 8 bits each value).

Related

Canvas cannot draw accurate color [duplicate]

A client required help with a program that extracts the dominant color of a product image.
I was able to quickly implement this in Javascript; the algorithm below only samples the central square of a 3x3 grid on the image for a quick estimate of the t-shirt color in the image.
var image = new Image();
image.onload = function() {
try {
// get dominant color by sampling the central square of a 3x3 grid on image
var dominantColor = getDominantColor();
// output color
$("#output").html(dominantColor);
}
catch(e) {
$("#output").html(e);
}
};
image.src = "sample_image.jpg";
function getDominantColor() {
// Copy image to canvas
var canvas = $("<canvas/>")[0];
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
// get pixels from the central square of a 3x3 grid
var imageData = canvas.getContext("2d").getImageData(canvas.width/3, canvas.height/3, canvas.width/3, canvas.height/3).data;
var colorOccurrences = {};
var dominantColor = "";
var dominantColorOccurrence = 0;
for(var i = 0; i < imageData.length; i += 4) {
var red = imageData[i];
var green = imageData[i+1];
var blue = imageData[i+2];
//var alpha = imageData[i+3]; // not required for this task
var color = RGBtoHEX({"red": red, "green": green, "blue": blue});
if(colorOccurrences[color] == undefined) {
colorOccurrences[color] = 1;
}
else {
colorOccurrences[color] ++;
if(colorOccurrences[color] > dominantColorOccurrence) {
dominantColorOccurrence = colorOccurrences[color];
dominantColor = color;
}
}
}
return dominantColor;
}
function RGBtoHEX(rgb) {
var hexChars = "0123456789ABCDEF";
return "#"
+ (hexChars[~~(rgb.red/16)] + hexChars[rgb.red%16])
+ (hexChars[~~(rgb.green/16)] + hexChars[rgb.green%16])
+ (hexChars[~~(rgb.blue/16)] + hexChars[rgb.blue%16]);
}
The image in question is this (preview below).
However, the results when this image is processed in the code above are varied across machines/browsers: #FF635E is what I see on my machine, running Windows7 and using Firefox 32. My client running Mac gets a result of #FF474B on Safari and #FF474C on Firefox 33.
Though the results are close, why are they ideally not the exact same? Does getImageData indeed vary depending on the local setup, or is the JPG data being interpreted differently on different machines?
Edit: This image isn't a one-off case. Such color variations were noticed across a range of the image that the client requested to process. My client and I obtained different results for the same set of images.
Yes. This fact is exploited by canvas fingerprinting:
The same HTML5 Canvas element can
produce exceptional pixels on a different web browsers, depending on
the system on which it was executed.
This happens for several reasons: at the image format level — web
browsers uses different image processing engines, export options,
compression level, final images may got different hashes even if they
are pixel-perfect; at the pixmap level — operating systems use
different algorithms and settings for anti-aliasing and sub-pixel
rendering. We don't know all the reasons, but we have already
collected more than a thousand unique signatures.

Javascript canvas images are coming out blocky when stacking on top of each other

So I'm randomly generating two types of clouds, and the clouds are intended to stack on top of each other a bit and move slowly (like clouds irl would).
So essentially I'm drawing a cloud image, clearing the area, moving the image over (by recreating it in a different position), and creating another cloud and repeating the process, but when the clouds are stacking on top of each other I'm getting a square area in between the stacked clouds. Not sure how to fix it.
Check this out: (see how the clouds have a block-like look as they stack over eachother)
http://testcloudsmc.bitballoon.com
Here's the full code:
https://repl.it/FTo8/3
And here's the most relevant parts of the code:
------------------------------------------------
var canvas; var ctx; var frameRate; var assets = [];
window.onload = function(){
canvas = document.getElementById('mycanvas');
ctx = canvas.getContext('2d');
frameRate = 1000/30; //30 fps
//Decides on a x position for clouds:
for(var f = 0;f<Math.floor(Math.random()*40)+10;f++){
cloudsarrayxpos[f] = Math.floor(Math.random()*800)+10;
}
/* Move/Render images */
for (var a = 0; a < 21; a++){
assets[a] = document.getElementById(a);
console.log(assets[a]);
};
/* CREATE OBJECTS FUNCTIONS: */
var clouds = function(x,y,h,w){
ctx.clearRect(x,y,w,h);
if((x>300)||(x<100)){
ctx.drawImage(assets[7],x,y,w,h);
}else{
ctx.drawImage(assets[20],x,y,w,h);
}
} //end of clouds function
function animate(){
for(var g = 0; g<cloudsarrayxpos.length;g++){
clouds(cloudsarrayxpos[g],(5*g),300,200);
cloudsarrayxpos[g]+=(Math.random()*.1)+.05; //changing the x position of each cloud in the array making it appear to be moving
}
window.requestAnimationFrame(animate);
}
}//end of window.onload
The problem is that you are clearing the canvas in each cloud spot before drawing it.
That means that the transparency (alpha) values of the cloud images don't come into play as each resulting pixel is composed only of the last image that was drawn on it.
To fix this, only clear the canvas once per animation cycle.
Specifically:
Remove ctx.clearRect from clouds function
Add this at the top of the animate function:
ctx.clearRect(0, 0, 1300, 600);

Canvas pixel manipulation optimization

I'm trying to do some real-time pixel manipulation (mode7-like transformations if you are interested) with canvas.
It works so: it takes a source image data that works like a frame buffer, it does some transformations and writes the result into a another 320x240 destination image data.
function transform() {
var src_pointer, dest_pointer;
var destData = dest_context.getImageData(0,0, 320, 240);
var imgdata = destData.data;
var sourceData= source_context.getImageData(0,0,320,240).data;
for (var y = 0; y< 240; y++) for (var x = 0; x< 320; x++) {
//*** DOING SOME TRANSFORMATIONS HERE, AND WRITE THE RESULT AT SX AND SY
//... Doesn't have impact in perfomance. Suspicious, huh?
dest_pointer = getPointer(x,y);
src_pointer = getPointer(sx, sy);
imgdata[dest_pointer] = sourceData[src_pointer];
imgdata[dest_pointer +1] = sourceData[src_pointer +1];
imgdata[dest_pointer +2] = sourceData[src_pointer +2];
// Alpha? Sad thing that canvas just handle 32bit image data. I don't really need it.
imgdata[dest_pointer +3] = sourceData[src_pointer +3];
}
dest_context.putImageData(destData,0,0);
}
//Function to help map a coordinate into image data array
function getPointer(x,y) {
return ( ( y * 320 ) + x ) * 4;
}
That have a poor perfomance if you execute it continuously (about 12 frames per second). Doing some profiling I discard an specific method bottleneck (getImageData and putImageData have a really little load time).
I used to think that issue was in the transformation section but the profiling throws me that the bottle neck was specifically in the pixel assignment. Maybe optimizing math operations can be possible (think that's hard, because that's in the border line between pure javascript and browser engine), but in array assignment is possible to optimize?

Is canvas getImageData method machine/browser dependent?

A client required help with a program that extracts the dominant color of a product image.
I was able to quickly implement this in Javascript; the algorithm below only samples the central square of a 3x3 grid on the image for a quick estimate of the t-shirt color in the image.
var image = new Image();
image.onload = function() {
try {
// get dominant color by sampling the central square of a 3x3 grid on image
var dominantColor = getDominantColor();
// output color
$("#output").html(dominantColor);
}
catch(e) {
$("#output").html(e);
}
};
image.src = "sample_image.jpg";
function getDominantColor() {
// Copy image to canvas
var canvas = $("<canvas/>")[0];
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
// get pixels from the central square of a 3x3 grid
var imageData = canvas.getContext("2d").getImageData(canvas.width/3, canvas.height/3, canvas.width/3, canvas.height/3).data;
var colorOccurrences = {};
var dominantColor = "";
var dominantColorOccurrence = 0;
for(var i = 0; i < imageData.length; i += 4) {
var red = imageData[i];
var green = imageData[i+1];
var blue = imageData[i+2];
//var alpha = imageData[i+3]; // not required for this task
var color = RGBtoHEX({"red": red, "green": green, "blue": blue});
if(colorOccurrences[color] == undefined) {
colorOccurrences[color] = 1;
}
else {
colorOccurrences[color] ++;
if(colorOccurrences[color] > dominantColorOccurrence) {
dominantColorOccurrence = colorOccurrences[color];
dominantColor = color;
}
}
}
return dominantColor;
}
function RGBtoHEX(rgb) {
var hexChars = "0123456789ABCDEF";
return "#"
+ (hexChars[~~(rgb.red/16)] + hexChars[rgb.red%16])
+ (hexChars[~~(rgb.green/16)] + hexChars[rgb.green%16])
+ (hexChars[~~(rgb.blue/16)] + hexChars[rgb.blue%16]);
}
The image in question is this (preview below).
However, the results when this image is processed in the code above are varied across machines/browsers: #FF635E is what I see on my machine, running Windows7 and using Firefox 32. My client running Mac gets a result of #FF474B on Safari and #FF474C on Firefox 33.
Though the results are close, why are they ideally not the exact same? Does getImageData indeed vary depending on the local setup, or is the JPG data being interpreted differently on different machines?
Edit: This image isn't a one-off case. Such color variations were noticed across a range of the image that the client requested to process. My client and I obtained different results for the same set of images.
Yes. This fact is exploited by canvas fingerprinting:
The same HTML5 Canvas element can
produce exceptional pixels on a different web browsers, depending on
the system on which it was executed.
This happens for several reasons: at the image format level — web
browsers uses different image processing engines, export options,
compression level, final images may got different hashes even if they
are pixel-perfect; at the pixmap level — operating systems use
different algorithms and settings for anti-aliasing and sub-pixel
rendering. We don't know all the reasons, but we have already
collected more than a thousand unique signatures.

Algorithm to create a bitmap/canvas image from binary 1's and 0's to visually see the data in JavaScript?

Let's say I have 20,164 binary digits in a standard JavaScript string, for example:
var data = '101010101010101010101010100110001011010101101' ...
What I want to do is see a visual representation of these digits by converting it to a bitmap or perhaps HTML5 canvas image. So if I loop through all the bits and it comes across a 1 it will draw a black pixel, and a 0 the pixel will be white.
So I'm guessing I'll need a 142 x 142 pixel grid something which looks like this:
What's an algorithm or way to do this in JavaScript? All I need to do is display it on the web page so maybe creating a basic bitmap or canvas or SVG image will be fine.
Many thanks
You're exactly correct with the HTML5 canvas idea. You could try something like the following if you don't want to do base64 data.
Javascript (with no error checking):
var string = "1010101...";
var ctx = document.getElementById("canvas").getContext('2d');
ctx.fillStyle = "#FFF"; // Fill with white first
ctx.fillRect(0, 0, 142, 142);
ctx.fillStyle = "#000";
for (var i = 0; i < 142; i++) { // Loop through each character
for (var j = 0; j < 142; j++) {
if (string[i*142+j] == '1') // If the character is one,
ctx.fillRect(i, j, 1, 1 ); // fill the pixel with black
}
}
HTML:
<body>
<canvas width=142 height=142 id="canvas"></canvas>
</body>
If you use this, you should make sure to check that the length of the string is the length you are expecting.
you can do something like
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var image = new Image();
image.src ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12NgoC4AAABQAAEiE+h1AAAAAElFTkSuQmCC";
image.onload = function() {
ctx.drawImage(image, 0, 0);
};
You may also include the image directly in the HTML without using JS:
Example:
<img alt='' src='data:image/gif;base64,R0lGODlhBgASALMAAOfn5+rq6uvr6+zs7O7u7vHx8fPz8/b29vj4+P39/f///wAAAAAAAAAAAAAAAAAAACwAAAAABgASAAAIMAAVCBxIsKDBgwgTDkzAsKGAhxARSJx4oKJFAxgzFtjIkYDHjwNCigxAsiSAkygDAgA7'/>
But the image must be in a correct format: jpg, tiff, png, etc. not just as a bitmap.
You may easily convert a bitmap to formats like BMP.

Categories

Resources