Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to work with 16bit Gray Scale #30

Closed
KlausSchaefers opened this issue Sep 26, 2018 · 14 comments
Closed

How to work with 16bit Gray Scale #30

KlausSchaefers opened this issue Sep 26, 2018 · 14 comments

Comments

@KlausSchaefers
Copy link

Hi,

I am very interested in using the UPNG lib to handle 16BIT (grayscale) images such as this:

http://cosmin.users.sourceforge.net/testing/lena_16g_lin.png

When using the lib, I saw that data is is array is 8bit. As I need to do some transformation in the 16BIt space, is there anyway to access the 16bit data?

Klaus

@photopea
Copy link
Owner

Hi Klaus,

your image is a 16-bit image and UPNG.js parses it correctly. The .data array is Uint8Array Typed Array, but it contains the correct 16-bit data (correct bits). The length of .data is 131328 Bytes, and with the area of 256x256 pixels, it corresponds to 2 bytes (16 bits) per pixel.

You can convert it into an array of values between 0 and 65535, by reading pairs of bytes and connecting them together (PNG uses Big Endian). You can create a Uint16Array over the same ArrayBuffer, but JS uses Little Endian, so you have to reorder bytes in each pair.

How else can I help you?

@RSully
Copy link

RSully commented Apr 1, 2019

I was about to create an issue for this but found this one.

To me, it would make sense if UPNG handled this correctly. If it sees an image is 16 bits, it should provide Uint16Array instead of Uint8Array and handle the byte order internally. Please re-open this issue for discussion.

@RSully
Copy link

RSully commented Apr 1, 2019

Additionally, I have an image that is leading me to some confusion:

Screen Shot 2019-04-01 at 11 00 26

This is a single channel 16 bit image, so there are (1440*721) 1,038,240 16-bit pixels. I don't understand how img.data has a length of 2,077,201 - I would expect 2,076,480.

@photopea
Copy link
Owner

photopea commented Apr 1, 2019

The number 2,077,201 is 2,076,480 + 721 (height). Each row of pixels in PNG contains an extra byte with filtering method. UPNG "unfilters" data, but we keep it in the same Typed Array, to avoid allocating a new array (if your decompressed image was 3 GB big, allocating extra 3 GB would be a waste of memory).

The property "data" contains the image data. It is accessible as bytes (octets), as it is the usual way of storing digital information in computers. It has nothing to do with color values of pixels - a color information of one pixel can have between 1 bit and 64 bits (8 bytes).

PNG format supports not only 16 bits per sample, but also 1 bit, 2 bits, 4 bits, and 8 bits per sample. There can also be 1, 2, 3 or 4 samples per pixel (combinations of gray and RGB, with and without transparency). If we made 20 special functions, that would return an array of values between 0-1, 0-3, 0-15, 0-255, 0-65535, it would make UPNG quite messy. That is why we provide only UPNG.toRGBA8(), which converts any PNG into RGBA image, 8 bits per sample.

If toRGBA8() is not appropriate for your use case, you can convert it into another structure yourself. Let me know if you need help with it.

@RSully
Copy link

RSully commented Apr 1, 2019

I'd definitely appreciate a bit of help.

That makes sense. Is the extra byte (for filtering method) stored at the end or the beginning of each row?

As you said, it would be messy to have lots of functions, but perhaps using a DataView would solve this issue? You could store the data once but allow accessing it in many ways.

@photopea
Copy link
Owner

photopea commented Apr 1, 2019

Filtering data is removed and the last 721 bytes of "data" are nonsense. So the first Width*Height*2 bytes are what you need. You can convert it to Uint16Array this way:

var area = img.width * img.height
var nimg = new Uint16Array(area);    // or just  nimg = [];
for(var i=0; i<area; i++)  nimg[i] = (img.data[i*2]<<8) | img.data[i*2+1] ;

@mortac8
Copy link

mortac8 commented Aug 23, 2020

Is it basically the same solution if I want to get 16-bits per channel for my RGBA PNGs? I'm looking for a Uint16Array with indexes containing 16-bit values r,g,b,a,r,g,b,a, ...

This line of code is blowing my mind:
for(var i=0; i<area; i++) nimg[i] = (img.data[i*2]<<8) | img.data[i*2+1] ;

@photopea
Copy link
Owner

@mortac8 It is just converting big endian 16-bit integers into little-endian.

@RalphCodesTheInternet
Copy link

Hi Photopea

Thanks for this solution. It has helped me a lot so far!

Unfortunately i'm stuck with the conversion to 16 bit. The resulting image contains only 0's. The 8 bit array before converting does not contain only 0's though. I've tried with several different images and they all result to being 0's.

Would you mind pointing me in the right direction to solve this?

@photopea
Copy link
Owner

@RalphCodesTheInternet What do you mean by a "conversion"? UPNG.js can not convert between 8 and 16 bit data.

@RalphCodesTheInternet
Copy link

RalphCodesTheInternet commented Sep 22, 2021

So basically I get a 16 bit PNG compressed image. The image was compressed by opencv and is sent to my JS side in base64 encoding.

To decode base64 I did the following:

var binary_string = window.atob(message.data); var len = binary_string.length; var bytes = new Uint8Array(len); for (var i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); }

I removed the header and finally used UPNG to decode the PNG like this:

var without_header = bytes.slice(12,bytes.length); var im = UPNG.decode(without_header);

This essentially yields an 8 bit image like you mentioned before. I tried to convert it to 16 bits like you showed above, but the image then just contains 0 values.

Filtering data is removed and the last 721 bytes of "data" are nonsense. So the first WidthHeight2 bytes are what you need. You can convert it to Uint16Array this way:

var area = img.width * img.height
var nimg = new Uint16Array(area);    // or just  nimg = [];
for(var i=0; i<area; i++)  nimg[i] = (img.data[i*2]<<8) | img.data[i*2+1] ;

@photopea
Copy link
Owner

UPNG.decode() should contain a 16-bit data in a Uint8Array buffer.

If you need 8-bit RGBA data (e.g. to display it in a HTML5 canvas), use UPNG.toRGBA8(), as described in our manual.

@RalphCodesTheInternet
Copy link

RalphCodesTheInternet commented Sep 22, 2021

I do not need to display it in a canvas, I only require the raw 16 bit values of each pixel. Oh yes, and this is a gray scale image, so there is only one channel. I just cant figure out why all the values are 0 if the array returned by UPNG contains actual values.

First console log is UPNG return, second log is the method you described to convert to 16 bit image.

Screenshot_20210922_154539

@photopea
Copy link
Owner

There are values [18,81,18,81 ... ], these values can not be converted to 0 if yo use my for loop.

(18<<8) | 81   // this is not a zero

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants