#графика #изображения #png #cpp
Мне нужно извлечь информацию о цветах из картинки в формате png, в том числе и альфа канал. Если открыть картинку в гекс-редакторе, то нужные мне данные будут находится в блоке IDAT. Правда они там зашифрованы. Может кто-нибудь знает как расшифровать данный блок средствами С++?
Ответы
Ответ 1
Они там не зашифрованы. Они упакованы:) Сам формат описан - секция IDAT и компрессия. Вот только ручками распаковывать - долго будет. Рекомендую использовать готовый проект - libpng, который скачайте под свою платформу. Либо используйте готовые обертки - ImageMagick, OpenCV, Qt - они предоставляют доступ к пикселям и дополнительной информации.Ответ 2
Есть очень хорошая и небольшая библиотека - Lodepng - использую для работы с текстурами в OpenGL. Дает довольно неплохие возможности.Ответ 3
Блок IDAT можно распаковать библиотекой zlib, которая есть везде, где только можно) На JavaScript это выглядит примерно так (вместе с декодированием пикселей): 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); }); Взято из: https://github.com/devongovett/png.js/blob/master/png-node.js#L171Ответ 4
Блок IDAT можно распаковать библиотекой zlib, которая есть везде, где только можно) Но что делать дальше - найти трудно. Ответ закопан в кодах, и неплохая реализация у "lodepng". Но всё же, так как эту часть трудно раскопать - опишу детальнее. Считываем (или пишем фильтр) все секции idat которые рядом Пропускаем 2 байта с idat (там метод сжатия указан cм. libpng.org) Распаковуем всё в один кусок + резервируем ещё буфер img для rgb(rgba) png_postProcessScanlines(img,idat,image_width,image_height, 0); unfilter - (позволяет обьеденить некоторые повторяющиеся строки изображеня) remove padding bits - другими словами выравнивание Adam7 deinterlice - делает "последовательное приближение", если установлен флаг interlace_method=8 в заголовке. Детально https://ru.wikipedia.org/wiki/Adam7 Реализацию с шага 4 по 7, я покажу (это и есть расшифровка 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; } После выполнения png_postProcessScanlines получаем картинку с байтами глубины bpp, которую уже не сложно вывести штатными средствами на экран. "лишние" связи убрал. Много процедур повыбрасывал, оставил только 7 png_postProcessScanlines, png_getBpp, png_unfilter, png_removePaddingBits, png_Adam7_getpassvalues, png_Adam7_deinterlace, png_paethPredictor. Использовать примерно так 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
Комментариев нет:
Отправить комментарий