8

So here I have a base64 encoded png image:

iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==

and I decoded it using atob(). It turns out to be:

PNG

IHDRo&åIDAT×cøÿÿ?Ã Ã Ð1ñXÍõ5ËÑIEND®B`

Is it possible to get out the color values from this string? (without using <canvas>)

PS: It seems like it is possible since I found a demo:
 http://labs.calyptus.eu/JSBin/Demo/Viewer.html
 But I am not sure how he did it.

Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
  • I have seen a PNG library written in JavaScript floating around somewhere, but it only encoded raw pixel data into a PNG image. You might be able to reverse it. I think using a Canvas will be much easier. – Blender Aug 05 '12 at 01:02
  • Have you looked at the source of the page you linked to that is able to do it? – icktoofay Aug 05 '12 at 01:06

3 Answers3

4

In the source of the page you pointed, there's a function that uses the PNG.js and some other utilities to do what you want:

function show(data){
    var png = new PNG(data);
    var img = document.getElementById('image'), limg = document.getElementById('largeimage');
    document.getElementById('nativeimage').src = 'data:image/png;base64,' + data;
    img.innerHTML = '';
    limg.innerHTML = '';
    img.style.width = png.width + 'px';
    img.style.height = png.height + 'px';
    limg.style.width = (png.width * 3) + 'px';
    limg.style.width = (png.height * 3) + 'px';
    var line;
    while(line = png.readLine())
    {
        for (var x = 0; x < line.length; x++){
            var px = document.createElement('div'), px2 = document.createElement('div');
            px.className = px2.className = 'pixel';
            px.style.backgroundColor = px2.style.backgroundColor = '#' + line[x].toString(16).padRight('0', 6);
            img.appendChild(px);
            limg.appendChild(px2);
        }
    }
}

If you look at the loop in this function , you will notice that it's reading the PNG, line by line and ploting the pixels.

A simplified example would be:

var png = new PNG(data); // data is the base64 encoded data
var line;
var y = 0;
while(line = png.readLine())
{
    for (var x = 0; x < line.length; x++){
        var pixel = doSomethingWithPixelData(x, y, '#' + line[x].toString(16).padRight('0', 6));
    }
    y++;
}

function doSomethingWithPixelData(x, y, color) {
    // guess what? do something with pixel data here ;)
}
Ricardo Souza
  • 16,030
  • 6
  • 37
  • 69
  • Really nice! I will sure look into that. – Derek 朕會功夫 Aug 05 '12 at 02:37
  • Yes it works! http://jsfiddle.net/DerekL/6VqwC/ However, it sometimes will show `Filter 2/3/4 not implemented` when I try to put in larger images. Don't know why but it works for smaller PNGs. – Derek 朕會功夫 Aug 05 '12 at 03:06
  • 1
    This is a limitation of this script. The filters wasn't all implemented in javascript, yet. Try searching for an updated version of the script. – Ricardo Souza Aug 05 '12 at 19:44
  • [He](http://blog.calyptus.eu/seb/2009/05/png-parser-in-javascript/) said "I skipped interlacing, alpha and some of the filters for the demo. It’s not meant to be a fully working prototype nor a reference library in any way." So I think he will not update it. – Derek 朕會功夫 Aug 07 '12 at 00:42
  • Which PNG.js is this? https://www.npmjs.com/package/png.js doesn't seem to have a readline function and its parse function doesn't seem to support base64 data. – Brad Johnson Dec 01 '20 at 17:15
1

No, you cannot get the color values from that string directly. You must decode the PNG image data somehow (it is compressed). Using <canvas> is one way to do that.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 2
    I don't think there's anything magical about PNG or base64 encoding that makes it impossible to read with JavaScript. Base64 encoding is easy enough to reverse. And although it might be time-consuming, anyone who's willing to take the time to read up on the PNG specification will be able implement a function to read the color values from PNG data in JavaScript. Heck, you can even do it manually with a hex editor and a calculator. – Lèse majesté Aug 05 '12 at 01:13
  • 1
    Yes, it's possible, but not *directly* as I said. Yes, you can implement PNG decoding in Javascript. It would be annoying and difficult, and somebody's surely already done it. – Greg Hewgill Aug 05 '12 at 01:14
  • But what I'm talking about _is_ doing it directly (the same way any other image library would do it: by looking at the binary data and decoding it per the PNG specs). Writing an image library from scratch is always hard work and likely not the best solution, but it's still possible to read the PNG data directly. An indirect way of doing it would be to let the browser or some other piece of software decode the PNG into a more accessible format, such as a pixel array in Canvas. – Lèse majesté Aug 05 '12 at 01:25
  • I guess you're using a different sense of "directly". I meant that there is no way to recover an *individual* pixel colour value from the raw PNG image data, without actually decoding the PNG image first. By contrast, it *would* be possible (although still annoying) to get a particular pixel colour value from uncompressed BMP data, by working out the correct offset and getting the bytes at that location in the string. – Greg Hewgill Aug 05 '12 at 01:27
  • Well, yes, in that sense you're right. You'd have to process the entire PNG image to decompress it first before you can grab a particular pixel value. – Lèse majesté Aug 05 '12 at 01:29
1

It can be useful to do this without having to rely on canvas or the browser render cycle. For example, if you need to do this work in a headless environment like a unit test runner.

This is possible with the help of a library called png.js

const PNGReader = require('png.js');

function parseDataImage(data: string) {
  console.log('Data is', data); // Data is data:image/png;base64,iVBORw0KGgoAAAANSUh...
  const base64 = data.split(',')[1];
  console.log('Base64 is', base64); // Base64 is iVBORw0KGgoAAAANSUh...
  const bytes = atob(base64); // Base64 Decode
  console.log('Bytes are', bytes); // Bytes are <Some binary data>
  const png = new PNGReader(bytes);
  png.parse((err, png) => {
    console.log('Pixels are', png.pixels); // Pixels are Buffer{0: 255, 1: 0, 2: 65, ...
  });
}
Brad Johnson
  • 1,776
  • 1
  • 20
  • 26