Image alpha on default Android browser - javascript

I'm trying to duotone an image and plaster it onto a canvas.
Works in desktop browsers:
Chrome
Firefox
Safari
Internet Explorer
Fails in mobile browsers:
Android
Workable demo on JSFiddle, this example works in Chrome but fails in Android's default browser.
The code is:
<style>
body {
background-color: gray;
}
</style>
<canvas id="mycanvas" width="64" height="64"></canvas>
<script>
var image = new Image();
image.src = 'image.png';
image.onload = function () { //once the image finishes loading
var context = document.getElementById("mycanvas").getContext("2d");
context.drawImage(image, 0, 0);
var imageData = context.getImageData(0, 0, 64, 64);
var pixels = imageData.data;
var numPixels = pixels.length;
for (var i = 0; i < numPixels; i++) { //for every pixel in the image
var index = i * 4;
var average = (pixels[index] + pixels[index + 1] + pixels[index + 2]) / 3;
pixels[index] = average + 255; //red is increased
pixels[index + 1] = average; //green
pixels[index + 2] = average; //blue
//pixels[index + 3] = pixels[index + 3]; //no changes to alpha
}
context.clearRect(0, 0, 64, 64); //clear the image
context.putImageData(imageData, 0, 0); //places the modified image instead
}
</script>
The summary is:
set the background color to gray so alpha can be observed easier
create a canvas 64 by 64
load a image of a smiley face on a transparent background
draw the image onto the canvas
get the image's data
for every pixel, make the red stronger
replace the altered image on the canvas
The smiley face looks like this (block-quoted so you can tell it's transparent):
However, in comparison with the chrome and android browser,
The background of the android drawing is reddish while the chrome drawing is completely transparent.
So...
What happened here?
How can I change the code so that the android drawing matches with the chrome drawing?
Note: I already tried if (pixels[index + 3] == 0) continue;, and I'm aware of this, but it won't work for images with varying opacity.

Ok, I have a result.
First of all look at your loop:
for (var i = 0; i < numPixels; i++) { //for every pixel in the image
var index = i * 4;
index will be more than 3 times bigger than numPixels. And will get undefined values. And average will be NaN. But, then I fixed this - result was the same.
So I tried to use fillStyle and fillRect for every pixel. All becomes fine. My code:
function putImageData(ctx, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
var data = imageData.data;
var height = imageData.height;
var width = imageData.width;
dirtyX = dirtyX || 0;
dirtyY = dirtyY || 0;
dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width;
dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height;
var limitBottom = dirtyY + dirtyHeight;
var limitRight = dirtyX + dirtyWidth;
for (var y = dirtyY; y < limitBottom; y++) {
for (var x = dirtyX; x < limitRight; x++) {
var pos = y * width + x;
//adding red increased here
ctx.fillStyle = 'rgba(' + data[pos * 4 + 0] + 255 + ',' + data[pos * 4 + 1] + ',' + data[pos * 4 + 2] + ',' + (data[pos * 4 + 3] / 255) + ')';
ctx.fillRect(x + dx, y + dy, 1, 1);
}
}
};
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gcVBAYmslkm5QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABc0lEQVR42u2a3QrDMAiFZ9j7v7K7CpTRVtt4NCEnsJt1BP1y4l8nqvrZebXP5osACIAACIAACIAACIAACIAACIAAqpaIlPXkgpwHiIiqqtw5efa8f7csgIgTVVU5AlwGAELOSAjfLKndOWFBQyohVAHWPR/dCwEBCiDCYDSIViH5p/v87xUZZ8IUkJHGEGpoyLu/QjZomffeE+37xwthFH5aKdwNtQyOyhjT1QGzXosWaYBljMdY72laPUZZDLBq+6jTPO41xRXwGB/1Gw5ECOC6StxSASMQ2o5OQ9Jg5VxviYFI5KxgSQBXzmYNPlLaYctw72jr7ncd2DQx4KkCKud/cACeQNghvAmaCDjhCvBCOFODJX2Pgkp7gben+vR5pAqgL0aip8KI2ABNgyMRGzFiT1FARI+eWTDB3w7P6nhKHVBd5pYDGFVABsCU/wccHblLaxUNEwzACvKHB8EVFoeiBEAABEAABEAABEAABEAAe64fre4tZAeLAbcAAAAASUVORK5CYII=';
image.onload = function () {
var context = document.getElementById("mycanvas").getContext("2d"),
imageData;
context.drawImage(image, 0, 0);
imageData = context.getImageData(0, 0, 64, 64);
putImageData(context, imageData, 0, 0);
}

Related

How to convert png base 64 to pixel array with out using image.src or image.onload?

I was wondering how to load base64 on sites that have CSPs that don't allow it. Meaning we can't do stuff like:
image.src = "data:image/png;base64..."
So I figured I needed to find a different approach. I tried converting the base64 into a binary string then working from that to get a UintArray containing the pixel image data.
I don't know how to get and image out of the loadImage function, nor do I know what I'm doing wrong when getting the pixel(s).
I know that the conversion from the binary string to the pixel array is wrong, base64 must have some type of way of converting it, that is unknown to most of us.
Here is my code that tries to solve this:
var canvas = document.getElementById("canvas");
var imageBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAB4CAYAAAC0CXGGAAAD2ElEQVR4Xu1cMWhTURRNXAJ1l8QhlWwBoRahOIlOpS4tNF1cig6CQ4SOGnAQomPADIKD0sWlKbRLSyfFSQJSC0K2YDPY4G6hi7p6z4d7uPxXCfFk+7nvv5/zzrnv3Xvf+ykWgp9er/f771sajUYx2IVpnre/8MPzPhDB5u1PACZeQkjxu/aRUcHd1py5ZoBS90cllPqBqfsTgImTEKMYp8HUAKL9ZyQkAGRlTj1A08/A4eGhG+rMz8/nCYUK0f5xnaEMRB8QRRPtXwBwhCdOQvgDW62Wif/L5bJp0u/3zXW1WnVVNBqNjH1hYcFcj8djc91ut91ogYYSAgB8iAEYkIyEmGSaO29djbduLLn29qd9195duRfyCQHIzFpiIKeEMFrEET45OXE1jPM4NsZ1BO2VSsXtPxxKCACMgBiAhImGEugTGP6y2AglyGIfDA5ZnUkAxACEz8kl1O12TT7w/s0PI+uLV7+Z659fr7jzOGt/+/4lc3+z2cyXDwgA8CEGWD6AAkYJoR19on7nl+sDg70Lxo6ax5uT+4AAiAGrgXAosbu762t8MHDt9XrdtS8vLxt78lhIAMQA8QGWE7Ny+MHBgavxxcVF186KxblzYgEQA/Z4D91ieri/ZjT7oPzEXKPm2Tw/gHUCfeL1+Lnp/9XSlrsuCIAYAKc+dwnhJD57dN18FfWB47nP7rqQ3AcEAGYhMRDNgZ99eWRuQR+YeTzravr0xbGxIwNPr71078ccObMOsCReAMSALTXSUILtiW1ubuZaB9bX113N455ZOB8QABhfjEbFAAoQc+JoGSUaC7H2rE5EnVgAiA+IAVYHQgnhmTjMyKKxEObEeKILfQB9tigAUIkTA3AsMywhDB3Y+Z/UVQnUOJ4EwNgo4wMCQOr/rDL3/zFwc3vVnIVY+37LDAKejcYRwhFlK2u0PZ4v2rr8wfyEogBAnUcMgEbDEsqUDndmzFfYIebE0ZUYMzL0ueOVUzdnzviAAAQrc2KAhdOTVlYJ5wMCkLg";
canvas.width = 540;
canvas.height = 360;
var ctx = canvas.getContext('2d');
function loadImage(base64)
{
var binary_string = window.atob(base64.split(",")[1]);
var len = binary_string.length;
var pixels = new Uint8Array(len);
for (var i = 0; i < len; i++)
{
pixels[i] = binary_string.charCodeAt(i);
}
putImageData(ctx, {
data: pixels,
width: 100,
height: 100
}, 0, 0);
var img = new Image();
img.crossOrigin = "Anonymous";
console.log(pixels);
return img;
}
loadImage(imageBase64);
//ctx.drawImage(loadImage(imageBase64), 0, 0);
function putImageData(ctx, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
{
var data = imageData.data;
var height = imageData.height;
var width = imageData.width;
dirtyX = dirtyX || 0;
dirtyY = dirtyY || 0;
dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width;
dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height;
var limitBottom = dirtyY + dirtyHeight;
var limitRight = dirtyX + dirtyWidth;
for (var y = dirtyY; y < limitBottom; y++)
{
for (var x = dirtyX; x < limitRight; x++)
{
var pos = y * width + x;
ctx.fillStyle = 'rgba(' + data[pos * 4 + 0]
+ ',' + data[pos * 4 + 1]
+ ',' + data[pos * 4 + 2]
+ ',' + (data[pos * 4 + 3] / 255) + ')';
ctx.fillRect(x + dx, y + dy, 1, 1);
}
}
}
Maybe if we can find out how the base64 works then we can do it.
And with that:
Any help would be appreciated :)
Thanks!
Code for browserify here generated file (480kB):
var PNG = require('pngjs').PNG;
function getPNG(bin) {
return PNG.sync.read(bin);
}
function loadImage(base64)
{
var binary_string = Buffer.from(base64, 'base64');
var png = getPNG(binary_string);
return png;
}
window.getPNG = getPNG;
window.loadImage = loadImage;
And snippet demo:
<canvas id=canvas></canvas>
<script src="https://pastebin.com/raw/SN2vwZeg"></script>
<script>
var canvas = document.getElementById("canvas");
var imageBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAB4CAYAAAC0CXGGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMxSURBVHhe7doxaFNRGAXg6KRxlyRD4hpqqUEoTqJTaZcWTBeXoIPgEHcNCArR3TcIDpUsLq3QLi1Z1FECEitiVtshDe52jDbeM/Sd3ub3NtH7cvt/i6cgD6/5T9+9L+9MytHa2tovxD/K5bLzNQ4b9Xpn8efE0gX4Js4bz+ib+jaScbs2g2RIMzzu6+kI+RZeB6QZZePuAJOuryPkW/gdaLfbSHalUgnpZFyvrx1ImvA6wGq1WqwTmUwGyWi1WkhGPp9Hstvd3UUyZmdnkYxer4dk1Ov1of9GHSHfwuuANPPV9ddIdrVr80h29Y9bSHbR0h0kQ+qEjpBv4XWA90Jsb28PyY5nlnGnWDabRbLTvVDShNcBJp0PpL0Rk/Y+0v6f6Qj5Fn4HoiiKdeD9yg8k48Ll70jGz6+XkOykv3/z7kUko1qtagcS7fR1gHEnigt9JLvOZvz/jGeeaQeSLvwO8F5oY2MDya7T6SDZFYtFJLvFxUUkQ/dCSRdeB6QzsfQ8v9lsItnNzc0h2UnfN+iZOGnC78D9rWUk417mEZLBMy/9nuf7BHfiVe8ZkvFyfhXJ0A4kzenrACtsX0UyXDuwM/MJyU47kHThdUA6Az/9/ADJ4A6kHxaQ7Paf7yAZ3IHHV14g2fEZWUfIt/A6wPcB6TuxRqOBZLjeByqVCpIdf2em94GkCb8Drs+BXDsw6nMiHSHfJr8D0nMg7gC/E8dnYte9EJ+J+Z067gDTEfItvA7w3kd6/2fcz4UYv4vBeyMdId8mvwPX396KdWC5ewPJkN7/+dd7IX6/aDX3AcnQEfItvA6wwnoayeCZ5DOx616Iz8TcuZ2lfSQ7HSHfJr8D0nkgac+FmH4Crp/AqNfr9/vF6Wn8MJDuDF9AAsVWpL+FfPvvC+h2u8NL4Eg/Ad90Ab6JCxh36cZt5E+g0XBbYC6XO9jc0O2Wbk6DN6oRoFpFGIhvpv5OFCEc490KAhQXEI7xbRMB+B/MYgsYonH+JKs74LqAw6aGb55SRz5h+pl3T1PCBcVFSiPF4zPqAsbnyPdtMZEwhk/oJYz8l/hLGOdqww/bSimllFJKKaWUi1TqN5LWUoxlg3+MAAAAAElFTkSuQmCC"
var ctx = canvas.getContext('2d');
var png = loadImage(imageBase64.split(",")[1]);
putImageData(ctx, {
data: png.data,
width: png.width,
height: png.height
}, 0, 0);
function putImageData(ctx, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
{
var data = imageData.data;
var height = imageData.height;
var width = imageData.width;
dirtyX = dirtyX || 0;
dirtyY = dirtyY || 0;
dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width;
dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height;
var limitBottom = dirtyY + dirtyHeight;
var limitRight = dirtyX + dirtyWidth;
for (var y = dirtyY; y < limitBottom; y++)
{
for (var x = dirtyX; x < limitRight; x++)
{
var pos = y * width + x;
ctx.fillStyle = 'rgba(' + data[pos * 4 + 0]
+ ',' + data[pos * 4 + 1]
+ ',' + data[pos * 4 + 2]
+ ',' + (data[pos * 4 + 3] / 255) + ')';
ctx.fillRect(x + dx, y + dy, 1, 1);
}
}
}
</script>
Unlike how you maybe expecting, the binary data representing a PNG image are not an array of pixels. The binary data has headers and various meta data describing the file, then a compressed image.
If you need to get pixels out of a png binary without using the native browser decoding functionality, you either have to study the PNG format yourself or use a specialized library which will give you access to the pixels of the image.
Thee are many image libraries which support the PNG format. Following is a specialized library which can do the trick for you.
https://www.npmjs.com/package/pngjs
You can use Browserify to use this library in the front end.

JavaScript put Image data on top of canvas

I have the following code which creates a rectangle as shown in the image below:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<p id='one'></p>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 40, 40)
var imgData = ctx.createImageData(100, 100);
var i;
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
// ctx.putImageData(imgData, 10, 10);
</script>
</body>
</html>
When I make sure that the image data is added by not commenting ctx.putImageData , I get only the image data like the picture shown below:
How do I make sure that the image is created on top of the black rectangle in the sense that in the white spaces between the red lines?
I want to be able to see the black rectangle. I tried changing the alpha channel but that did not do anything.
Thanks in advance :).
putImageData will replace the targeted pixels with the ones contained in your ImageData, whatever they are.
This means that if your ImageData contains transparent pixels, these transparent pixels will be there once you put the ImageData on your canvas.
There are several ways to accomplish what you want though.
The one requiring the less code change, is to make your pixel manipulation from the current state of your context. By calling getImageData instead of createImageData, you will have an ImageData which contains the currently drawn pixels in the area you selected.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 40, 40);
// get the current pixels at (x, y, width, height)
var imgData = ctx.getImageData(10, 10, 100, 100);
var i;
// and do the pixel manip over these pixels
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 10, 10);
<canvas id="myCanvas"></canvas>
The main caveat is that getImageData is slower and requires more memory than createImageData, so if you are gonna do it every frame in an animation, it may not be suitable.
An other solution in this case (animation), is to use a second off-screen canvas, on which you will draw once your ImageData, an then draw this off-screen canvas on the main one using drawImage.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
var gridImage = generateGrid(100, 100);
var x = 0;
anim();
/*
Makes your pixel manip on an off-screen canvas
Returns the off-screen canvas
*/
function generateGrid(width, height){
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var off_ctx = canvas.getContext('2d');
var imgData = off_ctx.createImageData(width, height);
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
off_ctx.putImageData(imgData, 0,0);
return canvas;
}
function anim(){
ctx.clearRect(0,0,c.width, c.height);
x = (x + 1) % (c.width + 100);
ctx.fillRect(x, 20, 40, 40);
// and here you draw your generated canvas
ctx.drawImage(gridImage, x - 10, 0);
requestAnimationFrame(anim);
}
<canvas id="myCanvas"></canvas>
You could also refactor your code in order to make your normal drawings (here fillRect) after you did draw the ImageData, but behind current drawings, thanks to the globalCompositeOperation property of your context:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// do first your pixel manip
var imgData = ctx.createImageData(100, 100);
var i;
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 10, 10);
// change the gCO to draw behind current non-transparent pixels
ctx.globalCompositeOperation = 'destination-over';
// now make your normal drawings
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 40, 40);
// reset the gCO
ctx.globalCompositeOperation = 'source-over';
<canvas id="myCanvas"></canvas>
And finally, if you target only next-gen browsers, then you could make use of the ImageBitmap object, which will produce the same result as the off-screen canvas, in less lines of code:
(async () => {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
var gridImage = await generateGrid(100, 100);
var x = 0;
anim();
/*
Makes your pixel manip on an empty ImageData
Returns an Promise resolving to an ImageBitmap
*/
function generateGrid(width, height) {
var imgData = ctx.createImageData(width, height);
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
return createImageBitmap(imgData);
}
function anim() {
ctx.clearRect(0, 0, c.width, c.height);
x = (x + 1) % (c.width + 100);
ctx.fillRect(x, 20, 40, 40);
// and here you draw your generated canvas
ctx.drawImage(gridImage, x - 10, 0);
requestAnimationFrame(anim);
}
})();
<canvas id="myCanvas"></canvas>
I couldn't make it work with you exact code but when I tried with 2 images overlapping I managed to make them overlap.
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
</canvas>
<p id = 'one'></p>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = ctx.createImageData(100, 100);
var i;
for (i = 0; i < img.data.length; i += 6) {
img.data[i+0] = 255;
img.data[i+1] = 0;
img.data[i+2] = 0;
img.data[i+3] = 255;
}
var img2 = ctx.createImageData(100, 100);
var i;
for (i = 0; i < img2.data.length; i += 20) {
img2.data[i+0] = 0;
img2.data[i+1] = 255;
img2.data[i+2] = 0;
img2.data[i+3] = 255;
}
var pixels = 4*100*100;
var imgData1 = img.data;
var imgData2 = img2.data;
while (pixels--){
imgData1[pixels] = imgData1[pixels] * 0.5 + imgData2[pixels] *0.5;
}
img.data = imgData1;
ctx.putImageData(img, 10, 0);
</script>

How to change an HTML5 canvas to normal after grey scale conversion

I am converting an HTML5 canvas image to grey scale with the following code. Now I need to revert it back to normal after converting to grey scale, on a button click. How can I do that?
Here is my code.
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
data[i] = brightness;
// red
data[i + 1] = brightness;
// green
data[i + 2] = brightness;
// blue
// i+3 is alpha (the fourth element)
}
context.putImageData(imageData, 0, 0);

Understanding how canvas converts an image to black and white

I found this script for converting an image to black and white, which works great, but I was hoping to understand the code a little bit better. I put my questions in the code, in the form of comments.
Can anyone explain in a little more detail what is happening here:
function grayscale(src){ //Creates a canvas element with a grayscale version of the color image
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var imgObj = new Image();
imgObj.src = src;
canvas.width = imgObj.width;
canvas.height = imgObj.height;
ctx.drawImage(imgObj, 0, 0); //Are these CTX functions documented somewhere where I can see what parameters they require / what those parameters mean?
var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
for(var y = 0; y < imgPixels.height; y++){
for(var x = 0; x < imgPixels.width; x++){
var i = (y * 4) * imgPixels.width + x * 4; //Why is this multiplied by 4?
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3; //Is this getting the average of the values of each channel R G and B, and converting them to BW(?)
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
}
}
ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
return canvas.toDataURL();
}
The canvas functions are, like most functions, described in an official specification. Also, MDC is helpful for more "informal" articles. E.g. the drawImage function on MDC is here.
The getImageData function returns an object, which contains an array with the byte data of all pixels. Each pixel is described by 4 bytes: r, g, b and a.
r, g and b are the color components (red, green and blue) and alpha is the opacity. So each pixel uses 4 bytes, and therefore a pixel's data begins at pixel_index * 4.
Yes, it's averaging the values. Because in the next 3 lines r, g and b are all set to that same value, you'll obtain a gray color for each pixel (because the amount of all 3 components are the same).
So basically, for all pixels this will hold: r === g, g === b and thus also r === b. Colors for which this holds are grayscale (0, 0, 0 being black and 255, 255, 255 being white).
function grayscale(src){ //Creates a canvas element with a grayscale version of the color image
//create canvas
var canvas = document.createElement('canvas');
//get its context
var ctx = canvas.getContext('2d');
//create empty image
var imgObj = new Image();
//start to load image from src url
imgObj.src = src;
//resize canvas up to size image size
canvas.width = imgObj.width;
canvas.height = imgObj.height;
//draw image on canvas, full canvas API is described here http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html
ctx.drawImage(imgObj, 0, 0);
//get array of image pixels
var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
//run through all the pixels
for(var y = 0; y < imgPixels.height; y++){
for(var x = 0; x < imgPixels.width; x++){
//here is x and y are multiplied by 4 because every pixel is four bytes: red, green, blue, alpha
var i = (y * 4) * imgPixels.width + x * 4; //Why is this multiplied by 4?
//compute average value for colors, this will convert it to bw
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
//set values to array
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
}
}
//draw pixels according to computed colors
ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
return canvas.toDataURL();
}
In this function coefficient equal to 1/3 are used, however the usually used are: 0.3R + 0.59G + 0.11B (http://gimp-savvy.com/BOOK/index.html?node54.html).

Updating image data using putImageData on a resized canvas element

I have a static image (a png) that I'm drawing on a canvas element using drawImage.
var _canvas = null;
var _context = null;
var _curImageData;
function copyImageToCanvas(aImg)
{
_canvas = document.getElementById("canvas");
var w = aImg.naturalWidth;
var h = aImg.naturalHeight;
_canvas.width = w;
_canvas.height = h;
_context = _canvas.getContext("2d");
_context.clearRect(0, 0, w, h);
_context.drawImage(aImg, 0, 0);
_curImageData = _context.getImageData(0, 0, w, h);
}
I then manipulate the image data pixel by pixel (setting the opacity to 255 or 0 depending on the pixel value) and then update the canvas using putImageData.
function updateImage(maxPixelValToShow)
{
for (var x = 0; x < _curImageData.width; x++)
for (var y = 0; y < _curImageData.height; y++)
{
var offset = (y * _curImageData.width + x) * 4;
var r = _curImageData.data[offset];
var g = _curImageData.data[offset + 1];
var b = _curImageData.data[offset + 2];
var a = _curImageData.data[offset + 3];
var pixelNum = ((g * 255) + b);
//if greater than max or black/no data
if (pixelNum > maxPixelValToShow || (!r && !g && !b)) {
_curImageData.data[offset + 3] = 0;
} else {
_curImageData.data[offset + 3] = 255;
}
}
_context.putImageData(_curImageData, 0, 0);
}
And this all works perfectly. The issue I'm having is when I create a canvas twice the size of my image (or half the size, for that matter) and draw the image to fit the canvas size.
function copyImageToCanvas(aImg)
{
_canvas = document.getElementById("canvas");
var w = aImg.naturalWidth * 2; //double the size!
var h = aImg.naturalHeight * 2; //double the size!
_canvas.width = w;
_canvas.height = h;
_context = _canvas.getContext("2d");
_context.clearRect(0, 0, w, h);
_context.drawImage(aImg, 0, 0, w, h);
_curImageData = _context.getImageData(0, 0, w, h);
}
I'm getting lots of strange artifacts around the edges of my image when I call my updateImage function. Does anyone know a) why this is happening and b) what can I do about it?
Here are some images to show what is being generated:
The original image
The original image after my updateImage function call
The resized image
The resized image after my updateImage function call
[Solution]
Thanks Adam and sunetos, that was the problem. It worked fine once I setup one canvas to store the original file data and do the pixel manipulation and another only for display. Code snippet below:
function updateImage(maxPixelValToShow) {
//manipulate the _workingContext data
_workingContext.putImageData(_curImageData, 0, 0);
_displayContext.clearRect(0, 0, _workingCanvas.width*_scale, _workingCanvas.height*_scale);
_displayContext.drawImage(_workingCanvas, 0, 0, _workingCanvas.width*_scale, _workingCanvas.height*_scale);
}
It looks like the image smoothing used by the browser when scaling up the image in drawImage() antialiased the edges of your shape, causing new intermediate color values around the edge that are not handled by your updateImage() function. Like Adam commented, you should apply your updateImage() logic against the unmodified original pixels, and then scale it.

Categories