/* * Based on APNG Disassembler 2.3 * * Copyright (c) 2009 Max Stepin * maxst at users.sourceforge.net * * GNU LGPL information * -------------------- * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include "apngloader.h" #include #include #include #if defined(_MSC_VER) && _MSC_VER >= 1300 #define swap16(data) _byteswap_ushort(data) #define swap32(data) _byteswap_ulong(data) #elif __linux__ #include #define swap16(data) bswap_16(data) #define swap32(data) bswap_32(data) #else #define swap16(data) (((data >> 8) & 255) | ((data & 255) << 8)) #define swap32(data) ((swap16(data) << 16) | swap16(data >> 16)) #endif #define PNG_ZBUF_SIZE 32768 #define PNG_DISPOSE_OP_NONE 0x00 #define PNG_DISPOSE_OP_BACKGROUND 0x01 #define PNG_DISPOSE_OP_PREVIOUS 0x02 #define PNG_BLEND_OP_SOURCE 0x00 #define PNG_BLEND_OP_OVER 0x01 #define notabc(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) #define ROWBYTES(pixel_bits, width) \ ((pixel_bits) >= 8 ? \ ((width) * (((unsigned int)(pixel_bits)) >> 3)) : \ (( ((width) * ((unsigned int)(pixel_bits))) + 7) >> 3) ) unsigned char png_sign[8] = {137, 80, 78, 71, 13, 10, 26, 10}; int mask4[2]={240,15}; int shift4[2]={4,0}; int mask2[4]={192,48,12,3}; int shift2[4]={6,4,2,0}; int mask1[8]={128,64,32,16,8,4,2,1}; int shift1[8]={7,6,5,4,3,2,1,0}; unsigned int keep_original = 1; unsigned char pal[256][3]; unsigned char trns[256]; unsigned int palsize, trnssize; unsigned int hasTRNS; unsigned short trns1, trns2, trns3; unsigned int read32(std::istream& f1) { unsigned char a, b, c, d; f1.read((char*)&a, 1); f1.read((char*)&b, 1); f1.read((char*)&c, 1); f1.read((char*)&d, 1); return ((unsigned int)a<<24)+((unsigned int)b<<16)+((unsigned int)c<<8)+(unsigned int)d; } unsigned short read16(std::istream& f1) { unsigned char a, b; f1.read((char*)&a, 1); f1.read((char*)&b, 1); return ((unsigned short)a<<8)+(unsigned short)b; } unsigned short readshort(unsigned char * p) { return ((unsigned short)(*p)<<8)+(unsigned short)(*(p+1)); } void read_sub_row(unsigned char * row, unsigned int rowbytes, unsigned int bpp) { unsigned int i; for (i=bpp; i>1; for (i=bpp; i>1; } else { for (i=bpp; i>1; } } void read_paeth_row(unsigned char * row, unsigned char * prev_row, unsigned int rowbytes, unsigned int bpp) { unsigned int i; int a, b, c, pa, pb, pc, p; if (prev_row) { for (i=0; i>1] & mask4[i&1]) >> shift4[i&1]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0x11; *dp2++ = (a<<24) + g*0x111111; } break; case 2: for (i=0; i>2] & mask2[i&3]) >> shift2[i&3]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0x55; *dp2++ = (a<<24) + g*0x555555; } break; case 1: for (i=0; i>3] & mask1[i&7]) >> shift1[i&7]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0xFF; *dp2++ = (a<<24) + g*0xFFFFFF; } break; } } else /* PNG_BLEND_OP_OVER */ { switch (depth) { case 16: for (i=0; i>1] & mask4[i&1]) >> shift4[i&1]; if (g != trns1) { *dp1 = g*0x11; *dp2 = 0xFF000000+g*0x111111; } } break; case 2: for (i=0; i>2] & mask2[i&3]) >> shift2[i&3]; if (g != trns1) { *dp1 = g*0x55; *dp2 = 0xFF000000+g*0x555555; } } break; case 1: for (i=0; i>3] & mask1[i&7]) >> shift1[i&7]; if (g != trns1) { *dp1 = g*0xFF; *dp2 = 0xFF000000+g*0xFFFFFF; } } break; } } src += srcbytes; dst1 += dstbytes1; dst2 += dstbytes2; } } void compose2(unsigned char * dst1, unsigned int dstbytes1, unsigned char * dst2, unsigned int dstbytes2, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) { unsigned int i, j; unsigned int r, g, b, a; unsigned char * sp; unsigned char * dp1; unsigned int * dp2; for (j=0; j>1] & mask4[i&1]) >> shift4[i&1]; break; case 2: col = (sp[i>>2] & mask2[i&3]) >> shift2[i&3]; break; case 1: col = (sp[i>>3] & mask1[i&7]) >> shift1[i&7]; break; } b = pal[col][0]; g = pal[col][1]; r = pal[col][2]; a = trns[col]; if (bop == PNG_BLEND_OP_SOURCE) { *dp1++ = col; *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; } else /* PNG_BLEND_OP_OVER */ { if (a == 255) { *dp1++ = col; *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; } else if (a != 0) { if ((a2 = (*dp2)>>24)) { keep_original = 0; u = a*255; v = (255-a)*a2; al = 255*255-(255-a)*(255-a2); b2 = ((*dp2)&255); g2 = (((*dp2)>>8)&255); r2 = (((*dp2)>>16)&255); b = (b*u + b2*v)/al; g = (g*u + g2*v)/al; r = (r*u + r2*v)/al; a = al/255; } *dp1++ = col; *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; } else { dp1++; dp2++; } } } src += srcbytes; dst1 += dstbytes1; dst2 += dstbytes2; } } void compose4(unsigned char * dst, unsigned int dstbytes, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) { unsigned int i, j, step; unsigned int g, a, g2, a2; int u, v, al; unsigned char * sp; unsigned char * dp; step = (depth+7)/8; for (j=0; j>24)) { u = a*255; v = (255-a)*a2; al = 255*255-(255-a)*(255-a2); b2 = ((*dp)&255); g2 = (((*dp)>>8)&255); r2 = (((*dp)>>16)&255); b = (b*u + b2*v)/al; g = (g*u + g2*v)/al; r = (r*u + r2*v)/al; a = al/255; } *dp++ = (a << 24) + (r << 16) + (g << 8) + b; } else dp++; } } src += srcbytes; dst += dstbytes; } } int load_apng(std::stringstream& file, struct apng_data *apng) { unsigned int i, j; unsigned int rowbytes; int imagesize, zbuf_size, zsize, trns_idx; unsigned int len, chunk/*, crc, seq*/; unsigned int w, h, w0, h0, x0, y0; unsigned int frames, loops, first_frame, cur_frame; unsigned int outrow1, outrow2, outimg1, outimg2; unsigned short d1, d2; unsigned char c, dop, bop; unsigned char channels, depth, pixeldepth, bpp; unsigned char coltype, compr, filter, interl; z_stream zstream; memset(apng, 0, sizeof(struct apng_data)); for (i=0; i<256; i++) { pal[i][0] = i; pal[i][1] = i; pal[i][2] = i; trns[i] = 255; } zstream.zalloc = Z_NULL; zstream.zfree = Z_NULL; zstream.opaque = Z_NULL; inflateInit(&zstream); frames = 1; first_frame = 0; cur_frame = 0; zsize = 0; hasTRNS = 0; trns_idx = -1; x0 = 0; y0 = 0; loops = 0; bop = PNG_BLEND_OP_SOURCE; unsigned char sig[8]; unsigned char * pOut1; unsigned char * pOut2; unsigned char * pTemp; unsigned char * pData; unsigned char * pImg1; unsigned char * pImg2; unsigned char * pDst1; unsigned char * pDst2; unsigned short* frames_delay; file.read((char*)sig, 8); if(!file.eof() && memcmp(sig, png_sign, 8) == 0) { len = read32(file); chunk = read32(file); if ((len == 13) && (chunk == 0x49484452)) /* IHDR */ { w = w0 = read32(file); h = h0 = read32(file); file.read((char*)&depth, 1); file.read((char*)&coltype, 1); file.read((char*)&compr, 1); file.read((char*)&filter, 1); file.read((char*)&interl, 1); /*crc = */read32(file); channels = 1; if (coltype == 2) channels = 3; else if (coltype == 4) channels = 2; else if (coltype == 6) channels = 4; pixeldepth = depth*channels; bpp = (pixeldepth + 7) >> 3; rowbytes = ROWBYTES(pixeldepth, w); imagesize = (rowbytes + 1) * h; zbuf_size = imagesize + ((imagesize + 7) >> 3) + ((imagesize + 63) >> 6) + 11; /* * We'll render into 2 output buffers, first in original coltype, * second in RGBA. * * It's better to try to keep the original coltype, but if dispose/blend * operations will make it impossible, then we'll save RGBA version instead. */ outrow1 = w*channels; /* output coltype = input coltype */ outrow2 = w*4; /* output coltype = RGBA */ outimg1 = h*outrow1; outimg2 = h*outrow2; pOut1=(unsigned char *)malloc(outimg1); pOut2=(unsigned char *)malloc(outimg2); pTemp=(unsigned char *)malloc(imagesize); pData=(unsigned char *)malloc(zbuf_size); pImg1=pOut1; pImg2=pOut2; frames_delay = NULL; /* apng decoding - begin */ memset(pOut1, 0, outimg1); memset(pOut2, 0, outimg2); while (!file.eof()) { len = read32(file); chunk = read32(file); if (chunk == 0x504C5445) /* PLTE */ { unsigned int col; for (i=0; i= 0) memset(pDst1, trns_idx, w0); else keep_original = 0; break; case 4: memset(pDst1, 0, w0*2); break; case 6: memset(pDst2, 0, w0*4); break; } pDst1 += outrow1; pDst2 += outrow2; } } } } /*seq = */read32(file); w0 = read32(file); h0 = read32(file); x0 = read32(file); y0 = read32(file); d1 = read16(file); d2 = read16(file); file.read((char*)&dop, 1); file.read((char*)&bop, 1); /*crc = */read32(file); if(d2 == 0) d2 = 100; frames_delay[cur_frame] = (d1 * 1000)/d2; if (cur_frame == 0) { bop = PNG_BLEND_OP_SOURCE; if (dop == PNG_DISPOSE_OP_PREVIOUS) dop = PNG_DISPOSE_OP_BACKGROUND; } if (!(coltype & 4) && !(hasTRNS)) bop = PNG_BLEND_OP_SOURCE; rowbytes = ROWBYTES(pixeldepth, w0); cur_frame++; pImg1 += outimg1; pImg2 += outimg2; } else if (chunk == 0x49444154) /* IDAT */ { file.read((char*)(pData + zsize), len); zsize += len; /*crc = */read32(file); } else if (chunk == 0x66644154) /* fdAT */ { /*seq = */read32(file); len -= 4; file.read((char*)(pData + zsize), len); zsize += len; /*crc = */read32(file); } else if (chunk == 0x49454E44) /* IEND */ { pDst1 = pImg1 + y0*outrow1 + x0*channels; pDst2 = pImg2 + y0*outrow2 + x0*4; unpack(zstream, pTemp, imagesize, pData, zsize, h0, rowbytes, bpp); switch (coltype) { case 0: compose0(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; case 2: compose2(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; case 3: compose3(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; case 4: compose4(pDst1, outrow1, pTemp, rowbytes+1, w0, h0, bop, depth); break; case 6: compose6( pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; } break; } else { c = (unsigned char)(chunk>>24); if (notabc(c)) break; c = (unsigned char)((chunk>>16) & 0xFF); if (notabc(c)) break; c = (unsigned char)((chunk>>8) & 0xFF); if (notabc(c)) break; c = (unsigned char)(chunk & 0xFF); if (notabc(c)) break; file.seekg(len, std::ios_base::cur); /*crc = */read32(file); } } /* apng decoding - end */ if (coltype == 0) { switch (depth) { case 4: trns[1] *= 0x11; break; case 2: trns[1] *= 0x55; break; case 1: trns[1] *= 0xFF; break; } } inflateEnd(&zstream); apng->bpp = channels; apng->coltype = coltype; apng->last_frame = cur_frame; apng->first_frame = first_frame; apng->height = h; apng->width = w; apng->num_frames = frames; apng->num_plays = loops; apng->frames_delay = frames_delay; apng->pdata = pOut2; apng->bpp = 4; apng->coltype = 6; if(pData) free(pData); if(pTemp) free(pTemp); if(pOut1) free(pOut1); } else return -1; } else return -1; return 0; } void write_chunk(std::ostream& f, const char* name, unsigned char* data, unsigned int length) { unsigned int crc = crc32(0, Z_NULL, 0); unsigned int len = swap32(length); f.write((char*)&len, 4); f.write(name, 4); crc = crc32(crc, (const Bytef*)name, 4); if(data != NULL && length > 0) { f.write((char*)data, length); crc = crc32(crc, data, length); } crc = swap32(crc); f.write((char*)&crc, 4); } void write_IDATs(std::ostream& f, unsigned char* data, unsigned int length, unsigned int idat_size) { unsigned int z_cmf = data[0]; if((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) { if(length >= 2) { unsigned int z_cinfo = z_cmf >> 4; unsigned int half_z_window_size = 1 << (z_cinfo + 7); while(idat_size <= half_z_window_size && half_z_window_size >= 256) { z_cinfo--; half_z_window_size >>= 1; } z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); if(data[0] != (unsigned char)z_cmf) { data[0] = (unsigned char)z_cmf; data[1] &= 0xe0; data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f); } } } while(length > 0) { unsigned int ds = length; if(ds > PNG_ZBUF_SIZE) ds = PNG_ZBUF_SIZE; write_chunk(f, "IDAT", data, ds); data += ds; length -= ds; } } void save_png(std::stringstream& f, unsigned int width, unsigned int height, int channels, unsigned char *pixels) { unsigned int bpp = 4; unsigned char coltype = 0; if(channels == 3) coltype = 2; else if (channels == 2) coltype = 4; else if (channels == 4) coltype = 6; struct IHDR { unsigned int mWidth; unsigned int mHeight; unsigned char mDepth; unsigned char mColorType; unsigned char mCompression; unsigned char mFilterMethod; unsigned char mInterlaceMethod; } ihdr = { swap32(width), swap32(height), 8, coltype, 0, 0, 0 }; z_stream zstream1; z_stream zstream2; unsigned int i, j; unsigned int rowbytes = width * bpp; unsigned int idat_size = (rowbytes + 1) * height; unsigned int zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11; unsigned char* row_buf = (unsigned char*)malloc(rowbytes + 1); unsigned char* sub_row = (unsigned char*)malloc(rowbytes + 1); unsigned char* up_row = (unsigned char*)malloc(rowbytes + 1); unsigned char* avg_row = (unsigned char*)malloc(rowbytes + 1); unsigned char* paeth_row = (unsigned char*)malloc(rowbytes + 1); unsigned char* zbuf1 = (unsigned char*)malloc(zbuf_size); unsigned char* zbuf2 = (unsigned char*)malloc(zbuf_size); if(!row_buf || !sub_row || !up_row || !avg_row || !paeth_row || !zbuf1 || !zbuf2) return; row_buf[0] = 0; sub_row[0] = 1; up_row[0] = 2; avg_row[0] = 3; paeth_row[0] = 4; zstream1.data_type = Z_BINARY; zstream1.zalloc = Z_NULL; zstream1.zfree = Z_NULL; zstream1.opaque = Z_NULL; deflateInit2(&zstream1, Z_BEST_COMPRESSION, 8, 15, 8, Z_DEFAULT_STRATEGY); zstream2.data_type = Z_BINARY; zstream2.zalloc = Z_NULL; zstream2.zfree = Z_NULL; zstream2.opaque = Z_NULL; deflateInit2(&zstream2, Z_BEST_COMPRESSION, 8, 15, 8, Z_FILTERED); int a, b, c, pa, pb, pc, p, v; unsigned char* prev; unsigned char* row; f.write((char*)png_sign, 8); write_chunk(f, "IHDR", (unsigned char*)(&ihdr), 13); if(palsize > 0) write_chunk(f, "PLTE", (unsigned char*)(&pal), palsize * 3); if(trnssize > 0) write_chunk(f, "tRNS", trns, trnssize); zstream1.next_out = zbuf1; zstream1.avail_out = zbuf_size; zstream2.next_out = zbuf2; zstream2.avail_out = zbuf_size; prev = NULL; row = pixels; for(j = 0; j < (unsigned int)height; j++) { unsigned char* out; unsigned int sum = 0; unsigned char* best_row = row_buf; unsigned int mins = ((unsigned int)(-1)) >> 1; out = row_buf + 1; for(i = 0; i < rowbytes; i++) { v = out[i] = row[i]; sum += (v < 128) ? v : 256 - v; } mins = sum; sum = 0; out = sub_row + 1; for(i = 0; i < bpp; i++) { v = out[i] = row[i]; sum += (v < 128) ? v : 256 - v; } for(i = bpp; i < rowbytes; i++) { v = out[i] = row[i] - row[i - bpp]; sum += (v < 128) ? v : 256 - v; if(sum > mins) break; } if(sum < mins) { mins = sum; best_row = sub_row; } if(prev) { sum = 0; out = up_row + 1; for(i = 0; i < rowbytes; i++) { v = out[i] = row[i] - prev[i]; sum += (v < 128) ? v : 256 - v; if(sum > mins) break; } if(sum < mins) { mins = sum; best_row = up_row; } sum = 0; out = avg_row + 1; for(i = 0; i < bpp; i++) { v = out[i] = row[i] - prev[i] / 2; sum += (v < 128) ? v : 256 - v; } for(i = bpp; i < rowbytes; i++) { v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2; sum += (v < 128) ? v : 256 - v; if(sum > mins) break; } if(sum < mins) { mins = sum; best_row = avg_row; } sum = 0; out = paeth_row + 1; for(i = 0; i < bpp; i++) { v = out[i] = row[i] - prev[i]; sum += (v < 128) ? v : 256 - v; } for(i = bpp; i < rowbytes; i++) { a = row[i - bpp]; b = prev[i]; c = prev[i - bpp]; p = b - c; pc = a - c; pa = abs(p); pb = abs(pc); pc = abs(p + pc); p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; v = out[i] = row[i] - p; sum += (v < 128) ? v : 256 - v; if(sum > mins) break; } if(sum < mins) { best_row = paeth_row; } } zstream1.next_in = row_buf; zstream1.avail_in = rowbytes + 1; deflate(&zstream1, Z_NO_FLUSH); zstream2.next_in = best_row; zstream2.avail_in = rowbytes + 1; deflate(&zstream2, Z_NO_FLUSH); prev = row; row += rowbytes; } deflate(&zstream1, Z_FINISH); deflate(&zstream2, Z_FINISH); if(zstream1.total_out <= zstream2.total_out) write_IDATs(f, zbuf1, zstream1.total_out, idat_size); else write_IDATs(f, zbuf2, zstream2.total_out, idat_size); deflateReset(&zstream1); zstream1.data_type = Z_BINARY; deflateReset(&zstream2); zstream2.data_type = Z_BINARY; write_chunk(f, "IEND", 0, 0); deflateEnd(&zstream1); deflateEnd(&zstream2); free(zbuf1); free(zbuf2); free(row_buf); free(sub_row); free(up_row); free(avg_row); free(paeth_row); } void free_apng(struct apng_data *apng) { if(apng->pdata) free(apng->pdata); if(apng->frames_delay) free(apng->frames_delay); }