PNG format IDAT chunk, how to upload PNG

I need to extract color information from an image in the png format, including the alpha channel. If you open the image in the hex editor, the data I need will be located in the IDAT block. True, they are encrypted there. Can anyone know how to decrypt this block using C++?

Author: nick_n_a, 2013-07-15

4 answers

They are not encrypted there. They're packed:)

The format itself is described - IDAT section and compression.

That's just the handles to unpack-it will take a long time. I recommend using a ready-made project - libpng, which you can download for your platform. Or use ready-made wrappers - ImageMagick, OpenCV, Qt-they provide access to pixels and additional information.

 6
Author: KoVadim, 2013-07-15 14:52:45

There is a very good and small library - Lodepng - I use for working with textures in OpenGL. It gives quite good opportunities.

 3
Author: tkhalymon, 2013-07-21 13:04:56

The IDAT block can be unpacked with the zlib library, which is available wherever possible)

On JavaScript, it looks something like this (along with pixel decoding):

zlib.inflate(this.imgData, function(err, data) {
    var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft;
    if (err) throw err;
    pixelBytes = _this.pixelBitlength / 8;
    scanlineLength = pixelBytes * _this.width;
    pixels = new Buffer(scanlineLength * _this.height);
    length = data.length;
    row = 0;
    pos = 0;
    c = 0;
    while (pos < length) {
        switch (data[pos++]) {
            case 0:
                for (i = 0; i < scanlineLength; i += 1) {
                    pixels[c++] = data[pos++];
                }
                break;

            case 1:
                for (i = 0; i < scanlineLength; i += 1) {
                    byte = data[pos++];
                    left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                    pixels[c++] = (byte + left) % 256;
                }
                break;

            case 2:
                for (i = 0; i < scanlineLength; i += 1) {
                    byte = data[pos++];
                    col = (i - (i % pixelBytes)) / pixelBytes;
                    upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                    pixels[c++] = (upper + byte) % 256;
                }
                break;

            case 3:
                for (i = 0; i < scanlineLength; i += 1) {
                    byte = data[pos++];
                    col = (i - (i % pixelBytes)) / pixelBytes;
                    left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                    upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                    pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256;
                }
                break;

            case 4:
                for (i = 0; i < scanlineLength; i += 1) {
                    byte = data[pos++];
                    col = (i - (i % pixelBytes)) / pixelBytes;
                    left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                    if (row === 0) {
                        upper = upperLeft = 0;
                    } else {
                        upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                        upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)];
                    }
                    p = left + upper - upperLeft;
                    pa = Math.abs(p - left);
                    pb = Math.abs(p - upper);
                    pc = Math.abs(p - upperLeft);
                    if (pa <= pb && pa <= pc) {
                        paeth = left;
                    } else if (pb <= pc) {
                        paeth = upper;
                    } else {
                        paeth = upperLeft;
                    }
                    pixels[c++] = (byte + paeth) % 256;
                }
                break;
            default:
                throw new Error("Invalid filter algorithm: " + data[pos - 1]);
        }
        row++;
    }
    callback(pixels);
});

Taken from: https://github.com/devongovett/png.js/blob/master/png-node.js#L171

 3
Author: lampa, 2013-07-22 06:40:33

The IDAT block can be unpacked with the zlib library, which is available wherever possible.) But what to do next is hard to find. The answer is buried in the codes, and a good implementation of "lodepng". But still, since this part is difficult to unearth , I will describe it in more detail.

  1. We read (or write a filter) all the idat sections that are next to
  2. Skip 2 bytes with idat (the compression method is specified there, see libpng.org)
  3. Unpack everything in one piece + reserve another buffer img for rgb(rgba)
  4. png_postProcessScanlines(img,idat,image_width,image_height, 0);
  5. unfilter - (allows you to combine some duplicate lines of the image)
  6. remove padding bits - in other words, alignment
  7. Adam7 deinterlice-makes a "sequential approximation" if the interlace_method=8 flag is set in the header. Details https://ru.wikipedia.org/wiki/Adam7

The implementation from step 4 to 7, I will show (this is the decryption of IDAT)

unsigned png_postProcessScanlines(unsigned char* out, unsigned char* in, unsigned w, unsigned h, const void* info_png){
  unsigned err;
  /*  This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype.
  Steps:
  *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8)
  *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace
  NOTE: the in buffer will be overwritten with intermediate data! */
  //2017
  unsigned bpp = png_getBpp();//lodepng_get_bpp(&info_png->color);
  //2017 if(bpp == 0) return 31; /*error: invalid colortype*/

  if(interlace_method == 0){
    if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8){
      if ((err=png_unfilter(in, in, w, h, bpp))!=0) return err;
        png_removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h);
    }
    /*we can immediatly filter into the out buffer, no other steps needed*/
    else if ((err=png_unfilter(out, in, w, h, bpp))!=0) return err;
  }
  else /*interlace_method is 1 (Adam7)*/
  {
    unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8];
    unsigned i,j;
    png_Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
    for(i = 0; i < 7; i++)
    {
      if ((err=png_unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp))!=0) return err;
      /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline,
      move bytes instead of bits or move not at all*/
      if(bpp < 8)
      {
        /*remove padding bits in scanlines; after this there still may be padding
        bits between the different reduced images: each reduced image still starts nicely at a byte*/
        png_removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp,
                          ((passw[i] * bpp + 7) / 8) * 8, passh[i]);
      }
    }
    png_Adam7_deinterlace(out, in, w, h, bpp);        
  }
  return 0;
}

int png_getBpp(){
  //return getNumColorChannels(colortype) * bitdepth;
    switch(/*colortype*/ data.png.characteristics[1]/*Байт 0x19 с файла png*/){ /* [0]=bitdepth*/
    case 0: return 1 * data.png.characteristics[0];/*Байт 0x18 с файла png*/ /*grey*/
    case 2: return 3 * data.png.characteristics[0]; /*RGB*/
    case 3: return 1 * data.png.characteristics[0]; /*palette*/
    case 4: return 2 * data.png.characteristics[0]; /*grey + alpha*/
    case 6: return 4 * data.png.characteristics[0]; /*RGBA*/
  }

unsigned png_unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp){
  /*
  For PNG filter method 0
  this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times)
  out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline
  w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel
  in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes)
  */
  unsigned y;
  unsigned char* prevline = 0;
  unsigned err = 0;
  /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/
  size_t bytewidth = (bpp + 7) / 8;
  size_t linebytes = (w * bpp + 7) / 8;
  size_t i;

  for(y = 0; y < h; y++)
  {
    size_t outindex = linebytes * y;
    size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/
    unsigned char filterType = in[inindex];
    //__emit__(0xCC);
    //if ((err=png_unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes))!=0) return err;
    unsigned char* recon = out + outindex;
    const unsigned char* scanline = in + inindex + 1;
    const unsigned char* precon = prevline;
    size_t length  = linebytes;
   //unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned char filterType, size_t length

  switch(filterType)
  {
    case 0: for(i = 0; i < length; i++) recon[i] = scanline[i]; break;
    case 1:
      for(i = 0; i < bytewidth; i++) recon[i] = scanline[i];
      for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth];
      break;
    case 2:
      if(precon)
        for(i = 0; i < length; i++) recon[i] = scanline[i] + precon[i];
      else for(i = 0; i < length; i++) recon[i] = scanline[i];
      break;
    case 3:
      if(precon)
      {
        for(i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2;
        for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2);
      }
      else
      {
        for(i = 0; i < bytewidth; i++) recon[i] = scanline[i];
        for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2;
      }
      break;
    case 4:
      if(precon)
      {
        for(i = 0; i < bytewidth; i++)
          recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/
        for(i = bytewidth; i < length; i++)
          recon[i] = (scanline[i] + png_paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]));
      }
      else
      {
        for(i = 0; i < bytewidth; i++)
          recon[i] = scanline[i];
        for(i = bytewidth; i < length; i++)
          /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/
          recon[i] = (scanline[i] + recon[i - bytewidth]);
      }
      break;
    default: return 36; /*error: unexisting filter type given*/
    }
    prevline = &out[outindex];
  }
  return 0;
}


unsigned png_removePaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h)
{
  unsigned y;
  unsigned char bit;
  size_t diff = ilinebits - olinebits;
  size_t ibp = 0, obp = 0; /*input and output bit pointers*/
  for(y = 0; y < h; y++)
  {
    size_t x;
    for(x = 0; x < olinebits; x++){
      //bit = in[ibp >> 3 ] &   (1>> (7- (ibp & 7))); //unsigned char bit = readBitFromReversedStream(&ibp, in);
      //if (bit) out[obp >> 3] |=  (1>> (7- (obp & 7))); //setBitOfReversedStream(&obp, out, bit);
      if (in[ibp >> 3 ] &   (1>> (7- (ibp & 7)))) out[obp >> 3] |=  (1>> (7- (obp & 7)));
      ibp++; obp++;
    }
    ibp += diff;
  }
}

static const unsigned char ADAM7_IX[7] = {0,4,0,2,0,1,0}; 
static const unsigned char ADAM7_IY[7] = {0,0,4,0,2,0,1}; 
static const unsigned char ADAM7_DX[7] = {8,8,4,4,2,2,1}; 
static const unsigned char ADAM7_DY[7] = {8,8,8,4,4,2,2};

void png_Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp)
{ unsigned i;  /*Свернул циклы в define*/
  /*calculate width and height in pixels of each pass*/
  #define PNG_AD71L(i) if((passw[i]=(w+ (4-(i/2))*2 -  ((i&1)*( (5-i)+ (i==5)))  - 1  ) / ((4-(i/2))*2) )==0)passh[0]=0;\
                else if((passh[i]=(h+ (i<3)?8:(8-((i+1)&6)) - (i==2)?4:((i==4)?2:(i==6)?1:0)   -1 ) / ((i<3)?8:(8-((i+1)&6))))==0)passw[0]=0;
  PNG_AD71L(0);  PNG_AD71L(1);  PNG_AD71L(2);  PNG_AD71L(3);  PNG_AD71L(4);  PNG_AD71L(5);  PNG_AD71L(6);      
  filter_passstart[0] = padded_passstart[0] = passstart[0] = 0;
  #undef PNG_ADL71L
  #define PNG_ADL71L(i) filter_passstart[i + 1] = filter_passstart[i]+ ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0);/*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/\
  padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8);/*bits padded if needed to fill full byte at end of each scanline*/\
  passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; /*only padded at end of reduced image*/
  PNG_AD71L(0);  PNG_AD71L(1);  PNG_AD71L(2);  PNG_AD71L(3);  PNG_AD71L(4);  PNG_AD71L(5);  PNG_AD71L(6);
  #undef PNG_ADL71L      
}

void png_Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp)
{
  unsigned passw[7], passh[7];
  size_t filter_passstart[8], padded_passstart[8], passstart[8];
  unsigned i;
  png_Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
  if(bpp >= 8)
  {
    for(i = 0; i < 7; i++)
    {
      unsigned x, y, b;
      size_t bytewidth = bpp / 8;
      for(y = 0; y < passh[i]; y++)
      for(x = 0; x < passw[i]; x++)
      {
        size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth;
        size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth;
        for(b = 0; b < bytewidth; b++)
          out[pixeloutstart + b] = in[pixelinstart + b];
      }
    }
  }
  else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/
  {
    for(i = 0; i < 7; i++)
    {
      unsigned x, y, b;
      unsigned ilinebits = bpp * passw[i];
      unsigned olinebits = bpp * w;
      size_t obp, ibp; /*bit pointers (for out and in buffer)*/
      for(y = 0; y < passh[i]; y++)
      for(x = 0; x < passw[i]; x++)
      {
        ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp);
        obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp;
        for(b = 0; b < bpp; b++)
        {
          //unsigned char bit = readBitFromReversedStream(&ibp, in);
          /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/
          //setBitOfReversedStream0(&obp, out, bit);
          if (in[ibp >> 3 ] &   (1>> (7- (ibp & 7)))) out[obp >> 3] |=  (1>> (7- (obp & 7)));
          ibp++; obp++;
        }
      }
    }
  }
}

unsigned char png_paethPredictor(short a, short b, short c){
  short pa = abs(b - c);
  short pb = abs(a - c);
  short pc = abs(a + b - c - c);
  if(pc < pa && pc < pb) return (unsigned char)c;
  return (pb < pa)?(unsigned char)b:(unsigned char)a;
}

After executing png_postProcessScanlines, we get a picture with bytes of bpp depth, which is no longer difficult to display by standard means on the screen. I removed the "extra" connections. I threw out a lot of procedures, leaving only 7 png_postProcessScanlines, png_getBpp, png_unfilter, png_removePaddingBits, png_Adam7_getpassvalues, png_Adam7_deinterlace, png_paethPredictor.

Use something like this

if (tag == 1413563465 /*idat*/){
   d->Read((char*)&i,2); // Пропустить 2-ва байта. возможно проверить "метод" 
   i = data.png.image_width*data.png.image_height* png_getBpp();
   InitDeflate(d,8192);DeflateRead(&x_data,i+256); // Как-то расжать
   img = malloc(i);
   png_postProcessScanlines(img,x_data,image_width,image_height, 0);
   for (j=0;j<image_heigth;j++)
     for (i=0;j<image_width;i++) {
       // вывод img на экран
       }
   }

References

  1. Http://lodev.org/lodepng/

  2. Https://github.com/lvandeve/lodepng

  3. Http://www.libpng.org

 1
Author: nick_n_a, 2018-01-18 07:55:02