HTML5 Canvas to PNG zeroes all channels when alpha transparent - javascript

I have a Uint32Array I am trying to convert to a texture for WebGL. To do this I'm writing the array as RGBA values on a Canvas and getting a base64 encoded PNG from the canvas to send as a texture.
Whenever I set a pixel value to have an alpha of 0, the corresponding RGB channels are also zeroed upon conversion to a PNG. Is this an implementation detail? If I were to create PNGs in some other non-HTML5 program could I have an (RGBA) quadruplet of (255,255,255,0)? I tried using an alpha value of 1 and all other channels remain intact, so this is not an issue of premultiplied alpha.
Here is some javascript code to reproduce this effect:
var img = new Image();
var canvasObj = $('<canvas width="1" height="1"></canvas>');
var context = canvasObj[0].getContext('2d');
var imgd = context.getImageData(0,0,1,1);
var pix = imgd.data;
pix[0]=255; pix[1]=255; pix[2]=255; pix[3]=0;
context.putImageData(imgd,0,0);
img.src = canvasObj[0].toDataURL("image/png");
context.drawImage(img,0,0);
var imgd2 = context.getImageData(0,0,1,1);
var pix2 = imgd2.data;
pix2 will be all 0s.
Thanks!

It appears to be part of the PNG specification (http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html).
...fully transparent pixels should all be assigned the same
color value for best compression.
I couldn't find a direct source, but it seems like this particular implementation sets all the channels to zero.

Related

display .raw file image in browser

I have a image file in .raw format which is directly read from fingerprint scanner device. We have to display that in a browser using html and javascript. How can we convert the .raw image and display in the browser?
Following is the manual steps I used to convert using online tools
I am able to convert that hex content as .raw file using online converter http://tomeko.net/online_tools/hex_to_file.php?lang=en
and converted raw file can be converted again as jpeg file by https://www.iloveimg.com/convert-to-jpg/raw-to-jpg url
Sample file will look like this https://imgur.com/a/4snUAFL
I tried the following code to display hex content in the browser but didnt work.
function hexToBase64(str) {
return btoa(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
var img = new Image();
img.src = "data:image/jpeg;base64,"+hexToBase64(getBinary());
document.body.appendChild(img);
complete jsfiddle is http://jsfiddle.net/varghees/79NnG/1334/
First, what you have provided in your fiddle is probably not a .raw file.
While there are tons of different file formats using this extension, I don't quite bite the fact there is no metadata at all, since this is required to at least know the image's size.
So I'm sorry for future readers, but this answer only shows how to convert raw 8bit values into an actual image...
So now, without image size, but if the image is squared, we can actually do it from the byteLength only, (both width and height will be the square-root of the byteLength).
The general steps are
(convert your hex string to an actual Uint8Array)
set all 4th values of an Uint8ClampedArray 4 times bigger than the first Uint8Array (this will set the Alpha channel of our soon to be RGBA image)
pass this Uint8ClampedArray in the ImageData() constructor.
put this ImageData on a canvas
Tadaa!
So using a square full of random values (and thus avoid the hex to buffer conversion):
const fake = new Uint8Array( 256*256 );
crypto.getRandomValues(fake); // get random values
processSquareBitmap(fake.buffer);
function processSquareBitmap(buffer) {
const view = new Uint8Array(buffer);
const out = new Uint8ClampedArray(buffer.byteLength * 4);
const size = Math.sqrt(view.length);
if(size % 1) {
console.error('not a square');
return;
}
// set alpha channel
view.forEach((a,i)=>out[(i*4)+3] = a);
const image = new ImageData(out, size, size)
const canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
canvas.getContext('2d').putImageData(image, 0,0);
// if you want to save a png version
// canvas.toBlob(b=> saveAs(b, 'bitmap.png'));
document.body.appendChild(canvas);
}
But for not squared images, you must have the actual width and height.
I was able to deduce the ones of OP's hex data, and thus could make this fiddle which will display their image.

Convert ArrayBuffer into ImageData for drawing on canvas: optimization

I am streaming video over a WebSocket by sending each frame in the raw ImageData format (4 bytes per pixel in RGBA order). When I receive each frame on the client (as an ArrayBuffer), I want to paint this image directly onto the canvas as efficiently as possible, using putImageData.
This is my current solution:
// buffer is an ArrayBuffer representing a properly-formatted image
var array = new Uint8ClampedArray(buffer);
var image = new ImageData(array, width, height);
canvas.putImageData(image, 0, 0);
But it is rather slow. My theories as to why:
the array (which is ~1MB in size) is being copied thrice, once into the Uint8ClampedArray, once into the ImageData, and lastly into the canvas, each frame (30 times per second).
I am using new twice for each frame, which may be a problem for the garbage collector.
Are these theories correct and if so, what tricks can I employ to make this as fast as possible? I am willing to accept an answer that is browser-specific.
No, both your ImageData image and your TypedArray array share the exact same buffer buffer.
These are just pointers, your original buffer is never "copied".
var ctx = document.createElement('canvas').getContext('2d');
var buffer = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height).data.buffer;
var array = new Uint8ClampedArray(buffer);
var image = new ImageData(array, ctx.canvas.width, ctx.canvas.height);
console.log(array.buffer === buffer && image.data.buffer === buffer);
For your processing time issue, the best way would be to simply send directly the video stream to a videoElement and use drawImage.

How to scale alpha values in a canvas?

What's the best way to scale alpha values in a canvas?
The first problem I'm trying to solve is drawing a sprite that has intrinsic low alpha values. I want to draw it 3-4 times brighter than it really is. Currently I'm just drawing it 4 times in the same spot. (I cannot edit the image file and globalAlpha doesn't go above 1)
The second problem I'm trying to solve is drawing the boundary of multiple overlapping sprites. The sprites are circular but with squiggles. I figured I'd use this method combined with globalCompositeOperation = 'destination-out', but for that I need to maximize the alpha values for the second drawing.
As an option to markE's answer - you can simply scale the alpha channel directly.
I would only recommend this approach as a part of a pre-processing stage and not for use every time you need to use a sprite as iterating the buffer this way is a relatively slow process.
LIVE DEMO HERE
Assuming you already have the sprite in a canvas and know its position:
/// get the image data and cache its pixel buffer and length
var imageData = context.getImageData(x, y, width, height);
var data = imageData.data;
var length = data.length;
var i = 0;
var scale = 4; /// scale values 4 times. This may be a fractional value
/// scale only alpha channel
for(; i < length; i += 4) {
data[i + 3] *= scale;
}
context.putImageData(imageData, x, y);
The good thing with the Uint8ClampedArray which the canvas is using clamps and rounds the values for you so you do not need to check lower or upper bounds, nor convert the value to integer - the internal code do all this for you.
You can "brighten" an rgba color by flattening it to rgb and then increasing the rgb component values.
Convert the rgba value to rgb, also taking the background color into effect.
Increase the resulting red,green,blue values by a percentage to "brighten" the color.
Here's a function to do that (disclaimer: untested code here!):
function brighten(RGBA,bg,pct){
// convert rgba to rgb
alpha = 1 - RGBA.alpha/255;
red = Math.round((RGBA.alpha*(RGBA.red/255)+(alpha*(bg.red/255)))*255);
green = Math.round((RGBA.alpha*(RGBA.green/255)+(alpha*(bg.green/255)))*255);
blue = Math.round((RGBA.alpha*(RGBA.blue/255)+(alpha*(bg.blue/255)))*255);
// brighten the flattened rgb by a percentage (100 will leave the rgb unaltered)
redBright=parseInt( Math.min(255,red*pct/100) );
greenBright=parseInt( Math.min(255,green*pct/100) );
blueBright=parseInt( Math.min(255,blue*pct/100) );
return({red:redBright,green:greenBright,blue:blueBright});
}

HTML Canvas imageData array all 0's

var img, imageData,width,height;
var c = canvasEle.getContext("2d");
width = canvasEle.width;
height = canvasEle.height;
img = document.getElementById("id");
c.drawImage(img,0,0);
imageData = c.createImageData(width, height);
After I draw the image onto the context, then create an imageData array, the values of the array are all 0.
I have been struggling with this for hours and couldn't find any solution. The image is shown on the canvas after I draw it, but the imageData of the context says all the pixels are white. This doesn't make any sense to me.
With createImageData you are creating new image data for an empty image. Please use getImageData to get the image data from an already existing canvas

Capturing only a portion of canvas with .todataurl Javascript/HTML5

I can capture a full canvas with .todataurl without a problem. But I do not see or know if there is anyway to capture only a portion of the canvas and save that to image.
e.i. Mr. Potatohead script draws hats, hands feet faces etc etc. mixed all over the canvas and you can drag and drop them onto the mr potato in the center of the canvas. Press a button save the image of mr potato looking all spiffy to jpg for you. Without all the extra hats/feet/faces in the image.
I have resigned myself to the fact that this is impossible based on everything I've read. But you folks have proven to be smarter than google (or atleast google in my hands) a few times so i am taking a shot.
Sorry no code to post this time... unless you want this:
var canvas = document.getElementById("mrp");
var dataUrl = canvas.toDataURL();
window.open(dataUrl, "toDataURL() image", "width=800, height=600");
But that is just the example of dataurl i am working off of.. and it works outside of the fact it doesnt cap just the mr potato
My fallback is to pass the image to php and work with it there to cut out everything i dont want then pass it back.
EDIT
tmcw had a method for doing this. Not sure if its the way it SHOULD be done but it certainly works.
document.getElementById('grow').innerHTML="<canvas id='dtemp' ></canvas>";
var SecondaryCanvas = document.getElementById("dtemp");
var SecondaryCanvas_Context = SecondaryCanvas.getContext ("2d");
SecondaryCanvas_Context.canvas.width = 600;
SecondaryCanvas_Context.canvas.height = 600;
var img = new Image();
img.src = MainCanvas.toDataURL('image/png');
SecondaryCanvas_Context.drawImage(img, -400, -300);
var du = SecondaryCanvas.toDataURL();
window.open(du, "toDataURL() image", "width=600, height=600");
document.getElementById('grow').innerHTML="";
grow is an empty span tag, SecondaryCanvas is a var created just for this
SecondaryCanvas_Context is the getcontext of SecondaryCanvas
img created just to store the .toDataURL() of the main canvas containing the Mr. PotatoHead
drawImage with negative (-) offsets to move image of MainCanvas so that just the portion i want is showing.
Then cap the new canvas that was just created and open a new window with the .png
on and if you get an error from the script saying security err 18 its because you forgot to rename imgTop to img with the rest of the variables you copy pasted and chrome doesnt like it when you try to save local content images like that.
Here's a method that uses an off-screen canvas:
var canvas = document.createElement('canvas');
canvas.width = desiredWidth;
canvas.height = desiredHeight;
canvas.getContext('2d').drawImage(originalCanvas,x,y,w,h,0,0,desiredWidth, desiredHeight);
result = canvas.toDataURL()
Create a new Canvas object of a specific size, use drawImage to copy a specific part of your canvas to a specific area of the new one, and use toDataURL() on the new canvas.
A bit more efficient (and maybe a cleaner) way of extracting part of the image:
// x,y are position in the original canvas you want to take part of the image
// desiredWidth,desiredHeight is the size of the image you want to have
// get raw image data
var imageContentRaw = originalCanvas.getContext('2d').getImageData(x,y,desiredWidth,desiredHeight);
// create new canvas
var canvas = document.createElement('canvas');
// with the correct size
canvas.width = desiredWidth;
canvas.height = desiredHeight;
// put there raw image data
// expected to be faster as tere are no scaling, etc
canvas.getContext('2d').putImageData(imageContentRaw, 0, 0);
// get image data (encoded as bas64)
result = canvas.toDataURL("image/jpeg", 1.0)
you can give left,top,width and Height parameters to toDataURL function.. Here is the code to get data image depending on the object on canvas.
mainObj = "your desired Object"; // for example canvas._objects[0];
var image = canvas.toDataURL({ left: mainObj.left, top:mainObj.top,
width: mainObj.width*mainObj.scaleX, height: mainObj.height*mainObj.scaleY});

Categories

Resources