Canvas cannot draw accurate color [duplicate] - javascript

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.

Related

Rendering too many points on Javascript-player

As part of a project, I have to render a video on a JS-player from a text file which has the points - all the changed coordinates along-with the color in each frame. Below is the code I'm using to draw these point on the screen.
But the issue is that the number of changed pixels in each frame are as high as ~20,000 and I need to display these in less than 30ms (inter-frame time difference). So, when I run this code the browser hangs for almost each frame. Could someone suggest an improvement for this?
Any help is really appreciated.
c.drawImage(img,0,0,800,800);
setInterval(
function(){
while(tArr[index]==time) {
var my_imageData = c.getImageData(0,0,width, height);
color(my_imageData,Math.round(yArr[index]),Math.round(xArr[index]),Math.round(iArr[index]),255);
c.putImageData(my_imageData,0,0);
index=index+1;
}
time = tArr[index];
}
,30);
xArr, yArr, iArr, tArr are arrays of x-coordinate, y-coordinate, intensity value and time of appearance respectively for the corresponding point to be rendered
function color(imageData,x,y,i,a){ //wrapper function to color the point
var index = (x + y * imageData.width) * 4;
imageData.data[index+0] = i;
imageData.data[index+1] = i;
imageData.data[index+2] = i;
imageData.data[index+3] = a;
}

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.

Resizing a canvas image without blurring it

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).

Background acting odd html5 canvas

I have an 800 by 100 image for the background and I am trying to pull sections from it (like a sprite sheet) to generate a background because I am under the impression that it is an efficient way of doing it(also seems like one that I can toy with and learn to generate backgrounds in the future!)
It is not working though, int he first animation frame ( I found this from slowing it down) it shows half of the entire background image(not the sections that I want to use) then it moves it down presumably 800 pixels and shows it and by the third frame it is gone!
please help :/ thank you!
var bricks = [0, 1, 1, 1, 1, 2];
function createBackground() {
for(var i = 0; i < bricks.length; i++) {
drawBackground(bricks[i]);
}
}
var bg = new Image();
bg.src = 'bgsheet2.png';
var srcX, srcY = 0,srcW = 100,srcH = 100,destX = 0,destY = canvas.height-100,destW = 100,destH = 100;
function drawBackground(type) {
switch(type) {
case 1:
srcX = 0;
ctx.drawImage(bg,srcX,srcY,srcW,srcH,destX,destY,destW,destH);
destX+=100;
break;
case 2:
srcX = 100;
ctx.drawImage(bg,srcX,srcY,srcW,srcH,destX,destY,destW,destH);
destX+=100;
break;
case 3:
srcX = 200;
destX+=100;
ctx.drawImage(bg,srcX,srcY,srcW,srcH,destX,destY,destW,destH);
break;
default:
srcX = 300;
ctx.drawImage(bg,srcX,srcY,srcW,srcH,destX,destY,destW,destH);
destX+=100;
break;
}
}
//this is in my main animation loop
createBackground();
For the efficiency, you have the following common solutions.
If the background is static (no moving parts like water or any interaction with it) then you can draw it to an on-screen canvas (a visible canvas) just the one time, but don't clear that canvas. That way your tiles are only processed and drawn the once.
The second method, a little more difficult than the first, is to draw sections to an offscreen canvas with the goal of limiting your (re)draws calls.
You can also get in to using "dirty rectangles" which is basically the practice of only updating the changed parts of your display. This is by far the hardest solution for efficiency.

Categories

Resources