// swaps pixel packing order within bytes static unsafe void png_do_packswap(ref png_row_info row_info, byte[] row) { if (row_info.bit_depth < 8) { byte[] table; if (row_info.bit_depth == 1) { table = onebppswaptable; } else if (row_info.bit_depth == 2) { table = twobppswaptable; } else if (row_info.bit_depth == 4) { table = fourbppswaptable; } else return; fixed(byte *row_ = row) { byte *rp = row_ + 1; // skip filter value byte *end = rp + row_info.rowbytes; for (; rp < end; rp++) { *rp = table[*rp]; } } } }
// swaps red and blue bytes within a pixel static unsafe void png_do_bgr(ref png_row_info row_info, byte[] row) { fixed(byte *row_ = row) { byte *rp = row_ + 1; // skip filter value if ((row_info.color_type & PNG_COLOR_TYPE.COLOR_MASK) == PNG_COLOR_TYPE.COLOR_MASK) { uint row_width = row_info.width; if (row_info.bit_depth == 8) { if (row_info.color_type == PNG_COLOR_TYPE.RGB) { for (uint i = 0; i < row_width; i++, rp += 3) { byte save = *rp; * rp = *(rp + 2); *(rp + 2) = save; } } else if (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA) { for (uint i = 0; i < row_width; i++, rp += 4) { byte save = *rp; * rp = *(rp + 2); *(rp + 2) = save; } } } else if (row_info.bit_depth == 16) { if (row_info.color_type == PNG_COLOR_TYPE.RGB) { for (uint i = 0; i < row_width; i++, rp += 6) { byte save = *rp; * rp = *(rp + 4); *(rp + 4) = save; save = *(rp + 1); *(rp + 1) = *(rp + 5); *(rp + 5) = save; } } else if (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA) { for (uint i = 0; i < row_width; i++, rp += 8) { byte save = *rp; * rp = *(rp + 4); *(rp + 4) = save; save = *(rp + 1); *(rp + 1) = *(rp + 5); *(rp + 5) = save; } } } } } }
// swaps byte order on 16 bit depth images static unsafe void png_do_swap(ref png_row_info row_info, byte[] row) { if (row_info.bit_depth == 16) { fixed(byte *row_ = row) { byte *rp = row_ + 1; // skip filter value uint istop = row_info.width * row_info.channels; for (uint i = 0; i < istop; i++, rp += 2) { byte t = *rp; * rp = *(rp + 1); *(rp + 1) = t; } } } }
// invert monochrome grayscale data static unsafe void png_do_invert(ref png_row_info row_info, byte[] row) { uint istop = row_info.rowbytes; fixed(byte *row_ = row) { byte *rp = row_ + 1; // skip filter value // This test removed from libpng version 1.0.13 and 1.2.0: // if(row_info->bit_depth==1 && if (row_info.color_type == PNG_COLOR_TYPE.GRAY) { for (uint i = 0; i < istop; i++) { *rp = (byte)(~(*rp)); rp++; } } else if (row_info.color_type == PNG_COLOR_TYPE.GRAY_ALPHA && row_info.bit_depth == 8) { for (uint i = 0; i < istop; i += 2) { *rp = (byte)(~(*rp)); rp += 2; } } else if (row_info.color_type == PNG_COLOR_TYPE.GRAY_ALPHA && row_info.bit_depth == 16) { for (uint i = 0; i < istop; i += 4) { *rp = (byte)(~(*rp)); *(rp + 1) = (byte)(~(*(rp + 1))); rp += 4; } } } }
// If the bit depth < 8, it is expanded to 8. Also, if the already // expanded transparency value is supplied, an alpha channel is built. static unsafe void png_do_expand(ref png_row_info row_info, byte[] row) { int shift, value; uint row_width=row_info.width; if(row_info.color_type==PNG_COLOR_TYPE.GRAY) { if(row_info.bit_depth<8) { fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value ushort gray=0; switch(row_info.bit_depth) { case 1: { gray=(ushort)((gray&0x01)*0xff); sp+=(row_width-1)>>3; dp+=row_width-1; shift=7-(int)((row_width+7)&0x07); for(uint i=0; i<row_width; i++) { if(((*sp>>shift)&0x01)==0x01) *dp=0xff; else *dp=0; if(shift==7) { shift=0; sp--; } else shift++; dp--; } } break; case 2: { gray=(ushort)((gray&0x03)*0x55); sp+=(row_width-1)>>2; dp+=row_width-1; shift=(int)((3-((row_width+3)&0x03))<<1); for(uint i=0; i<row_width; i++) { value=(*sp>>shift)&0x03; *dp=(byte)(value|(value<<2)|(value<<4)|(value<<6)); if(shift==6) { shift=0; sp--; } else shift+=2; dp--; } } break; case 4: { gray=(ushort)((gray&0x0f)*0x11); sp+=(row_width-1)>>1; dp+=row_width-1; shift=(int)((1-((row_width+1)&0x01))<<2); for(uint i=0; i<row_width; i++) { value=(*sp>>shift)&0x0f; *dp=(byte)(value|(value<<4)); if(shift==4) { shift=0; sp--; } else shift=4; dp--; } } break; } row_info.bit_depth=8; row_info.pixel_depth=8; row_info.rowbytes=row_width; } } } }
// swaps byte order on 16 bit depth images static unsafe void png_do_swap(ref png_row_info row_info, byte[] row) { if(row_info.bit_depth==16) { fixed(byte* row_=row) { byte* rp=row_+1;// skip filter value uint istop=row_info.width*row_info.channels; for(uint i=0; i<istop; i++, rp+=2) { byte t=*rp; *rp=*(rp+1); *(rp+1)=t; } } } }
// Pack pixels into bytes. Pass the true bit depth in bit_depth. The // row_info bit depth should be 8 (one pixel per byte). The channels // should be 1 (this only happens on grayscale and paletted images). unsafe void png_do_pack(ref png_row_info row_info, byte[] row, uint bit_depth) { if(row_info.bit_depth!=8||row_info.channels!=1) return; fixed(byte* row_=row) { byte* sp=row_+1, dp=row_+1; // skip filter value uint row_width=row_info.width; int v=0; switch((int)bit_depth) { case 1: { int mask=0x80; for(uint i=0; i<row_width; i++) { if(*sp!=0) v|=mask; sp++; if(mask>1) mask>>=1; else { mask=0x80; *dp=(byte)v; dp++; v=0; } } if(mask!=0x80) *dp=(byte)v; } break; case 2: { int shift=6; for(uint i=0; i<row_width; i++) { byte value=(byte)(*sp&0x03); v|=(value<<shift); if(shift==0) { shift=6; *dp=(byte)v; dp++; v=0; } else shift-=2; sp++; } if(shift!=6) *dp=(byte)v; } break; case 4: { int shift=4; for(uint i=0; i<row_width; i++) { byte value=(byte)(*sp&0x0f); v|=(value<<shift); if(shift==0) { shift=4; *dp=(byte)v; dp++; v=0; } else shift-=4; sp++; } if(shift!=4) *dp=(byte)v; } break; } row_info.bit_depth=(byte)bit_depth; row_info.pixel_depth=(byte)(bit_depth*row_info.channels); row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_info.width); } }
unsafe void png_do_write_invert_alpha(png_row_info row_info, byte[] row) { fixed(byte* row_=row) { byte* sp=row_+1, dp=row_+1; // skip filter value uint row_width=row_info.width; if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { // This inverts the alpha channel in RGBA if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { sp+=3; dp=sp; *(dp++)=(byte)(255-*(sp++)); } } else // This inverts the alpha channel in RRGGBBAA { for(uint i=0; i<row_width; i++) { sp+=6; dp=sp; *(dp++)=(byte)(255-*(sp++)); *(dp++)=(byte)(255-*(sp++)); } } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA) { // This inverts the alpha channel in GA if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { *(dp++)=*(sp++); *(dp++)=(byte)(255-*(sp++)); } } else // This inverts the alpha channel in GGAA { for(uint i=0; i<row_width; i++) { sp+=2; dp=sp; *(dp++)=(byte)(255-*(sp++)); *(dp++)=(byte)(255-*(sp++)); } } } } }
// Shift pixel values to take advantage of whole range. Pass the // true number of bits in bit_depth. The row should be packed // according to row_info->bit_depth. Thus, if you had a row of // bit depth 4, but the pixels only had values from 0 to 7, you // would pass 3 as bit_depth, and this routine would translate the // data to 0 to 15. unsafe void png_do_shift(png_row_info row_info, byte[] row, png_color_8 bit_depth) { if(row_info.color_type==PNG_COLOR_TYPE.PALETTE) return; int[] shift_start=new int[4], shift_dec=new int[4]; uint channels=0; if((row_info.color_type&PNG_COLOR_TYPE.COLOR_MASK)==PNG_COLOR_TYPE.COLOR_MASK) { shift_start[channels]=row_info.bit_depth-bit_depth.red; shift_dec[channels]=bit_depth.red; channels++; shift_start[channels]=row_info.bit_depth-bit_depth.green; shift_dec[channels]=bit_depth.green; channels++; shift_start[channels]=row_info.bit_depth-bit_depth.blue; shift_dec[channels]=bit_depth.blue; channels++; } else { shift_start[channels]=row_info.bit_depth-bit_depth.gray; shift_dec[channels]=bit_depth.gray; channels++; } if((row_info.color_type&PNG_COLOR_TYPE.ALPHA_MASK)==PNG_COLOR_TYPE.ALPHA_MASK) { shift_start[channels]=row_info.bit_depth-bit_depth.alpha; shift_dec[channels]=bit_depth.alpha; channels++; } fixed(byte* row_=row) { // with low row depths, could only be grayscale, so one channel byte* bp=row_+1; // skip filter value if(row_info.bit_depth<8) { byte mask; uint row_bytes=row_info.rowbytes; if(bit_depth.gray==1&&row_info.bit_depth==2) mask=0x55; else if(row_info.bit_depth==4&&bit_depth.gray==3) mask=0x11; else mask=0xff; for(uint i=0; i<row_bytes; i++, bp++) { ushort v=*bp; *bp=0; for(int j=shift_start[0]; j>-shift_dec[0]; j-=shift_dec[0]) { if(j>0) *bp|=(byte)((v<<j)&0xff); else *bp|=(byte)((v>>(-j))&mask); } } } else if(row_info.bit_depth==8) { uint istop=channels*row_info.width; for(uint i=0; i<istop; i++, bp++) { int c=(int)(i%channels); ushort v=*bp; *bp=0; for(int j=shift_start[c]; j>-shift_dec[c]; j-=shift_dec[c]) { if(j>0) *bp|=(byte)((v<<j)&0xff); else *bp|=(byte)((v>>(-j))&0xff); } } } else { uint istop=channels*row_info.width; for(uint i=0; i<istop; i++) { int c=(int)(i%channels); ushort v=(ushort)(((ushort)(*bp)<<8)+*(bp+1)); ushort value=0; for(int j=shift_start[c]; j>-shift_dec[c]; j-=shift_dec[c]) { if(j>0) value|=(ushort)((v<<j)&(ushort)0xffff); else value|=(ushort)((v>>(-j))&(ushort)0xffff); } *bp++=(byte)(value>>8); *bp++=(byte)(value&0xff); } } } }
// remove filler or alpha byte(s) static unsafe void png_do_strip_filler(ref png_row_info row_info, byte[] row, PNG_FLAG flags) { fixed(byte* row_=row) { byte* sp=row_+1;// skip filter value byte* dp=row_+1;// skip filter value uint row_width=row_info.width; uint i; if(row_info.channels==4&&(row_info.color_type==PNG_COLOR_TYPE.RGB|| (row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA&&(flags&PNG_FLAG.STRIP_ALPHA)==PNG_FLAG.STRIP_ALPHA))) { if(row_info.bit_depth==8) { // This converts from RGBX or RGBA to RGB if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { dp+=3; sp+=4; for(i=1; i<row_width; i++) { *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; sp++; } } // This converts from XRGB or ARGB to RGB else { for(i=0; i<row_width; i++) { sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; } } row_info.pixel_depth=24; row_info.rowbytes=row_width*3; } else // if(row_info->bit_depth==16) { if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { // This converts from RRGGBBXX or RRGGBBAA to RRGGBB sp+=8; dp+=6; for(i=1; i<row_width; i++) { *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; sp+=2; } } else { // This converts from XXRRGGBB or AARRGGBB to RRGGBB for(i=0; i<row_width; i++) { sp+=2; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; *dp++=*sp++; } } row_info.pixel_depth=48; row_info.rowbytes=row_width*6; } row_info.channels=3; } else if(row_info.channels==2&&(row_info.color_type==PNG_COLOR_TYPE.GRAY|| (row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA&&(flags&PNG_FLAG.STRIP_ALPHA)==PNG_FLAG.STRIP_ALPHA))) { if(row_info.bit_depth==8) { // This converts from GX or GA to G if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { for(i=0; i<row_width; i++) { *dp++=*sp++; sp++; } } // This converts from XG or AG to G else { for(i=0; i<row_width; i++) { sp++; *dp++=*sp++; } } row_info.pixel_depth=8; row_info.rowbytes=row_width; } else // if(row_info->bit_depth==16) { if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { // This converts from GGXX or GGAA to GG sp+=4; dp+=2; for(i=1; i<row_width; i++) { *dp++=*sp++; *dp++=*sp++; sp+=2; } } else { // This converts from XXGG or AAGG to GG for(i=0; i<row_width; i++) { sp+=2; *dp++=*sp++; *dp++=*sp++; } } row_info.pixel_depth=16; row_info.rowbytes=row_width*2; } row_info.channels=1; } } if((flags&PNG_FLAG.STRIP_ALPHA)==PNG_FLAG.STRIP_ALPHA) row_info.color_type&=~PNG_COLOR_TYPE.ALPHA_MASK; }
// Add filler channel if we have RGB color static unsafe void png_do_read_filler(ref png_row_info row_info, byte[] row, uint filler, PNG_FLAG flags) { uint i; uint row_width=row_info.width; byte hi_filler=(byte)((filler>>8)&0xff); byte lo_filler=(byte)(filler&0xff); fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.GRAY) { if(row_info.bit_depth==8) { // This changes the data from G to GX if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { sp+=row_width; byte* dp=sp+row_width; for(i=1; i<row_width; i++) { *(--dp)=lo_filler; *(--dp)=*(--sp); } *(--dp)=lo_filler; row_info.channels=2; row_info.pixel_depth=16; row_info.rowbytes=row_width*2; } else // This changes the data from G to XG { sp+=row_width; byte* dp=sp+row_width; for(i=0; i<row_width; i++) { *(--dp)=*(--sp); *(--dp)=lo_filler; } row_info.channels=2; row_info.pixel_depth=16; row_info.rowbytes=row_width*2; } } else if(row_info.bit_depth==16) { // This changes the data from GG to GGXX if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { sp+=row_width*2; byte* dp=sp+row_width*2; for(i=1; i<row_width; i++) { *(--dp)=hi_filler; *(--dp)=lo_filler; *(--dp)=*(--sp); *(--dp)=*(--sp); } *(--dp)=hi_filler; *(--dp)=lo_filler; row_info.channels=2; row_info.pixel_depth=32; row_info.rowbytes=row_width*4; } else // This changes the data from GG to XXGG { sp+=row_width*2; byte* dp=sp+row_width*2; for(i=0; i<row_width; i++) { *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=hi_filler; *(--dp)=lo_filler; } row_info.channels=2; row_info.pixel_depth=32; row_info.rowbytes=row_width*4; } } } // COLOR_TYPE==GRAY else if(row_info.color_type==PNG_COLOR_TYPE.RGB) { if(row_info.bit_depth==8) { // This changes the data from RGB to RGBX if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { sp+=row_width*3; byte* dp=sp+row_width; for(i=1; i<row_width; i++) { *(--dp)=lo_filler; *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); } *(--dp)=lo_filler; row_info.channels=4; row_info.pixel_depth=32; row_info.rowbytes=row_width*4; } else // This changes the data from RGB to XRGB { sp+=row_width*3; byte* dp=sp+row_width; for(i=0; i<row_width; i++) { *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=lo_filler; } row_info.channels=4; row_info.pixel_depth=32; row_info.rowbytes=row_width*4; } } else if(row_info.bit_depth==16) { // This changes the data from RRGGBB to RRGGBBXX if((flags&PNG_FLAG.FILLER_AFTER)==PNG_FLAG.FILLER_AFTER) { sp+=row_width*6; byte* dp=sp+row_width*2; for(i=1; i<row_width; i++) { *(--dp)=hi_filler; *(--dp)=lo_filler; *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); } *(--dp)=hi_filler; *(--dp)=lo_filler; row_info.channels=4; row_info.pixel_depth=64; row_info.rowbytes=row_width*8; } else // This changes the data from RRGGBB to XXRRGGBB { sp+=row_width*6; byte* dp=sp+row_width*2; for(i=0; i<row_width; i++) { *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=hi_filler; *(--dp)=lo_filler; } row_info.channels=4; row_info.pixel_depth=64; row_info.rowbytes=row_width*8; } } } // COLOR_TYPE==RGB } }
static unsafe void png_do_read_invert_alpha(ref png_row_info row_info, byte[] row) { uint row_width=row_info.width; fixed(byte* row_=row) { byte* sp=row_+row_info.rowbytes+1; // skip filter value byte* dp=row_+row_info.rowbytes+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { // This inverts the alpha channel in RGBA if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { *(--dp)=(byte)(255-*(--sp)); sp-=3; dp=sp; } } else // This inverts the alpha channel in RRGGBBAA { for(uint i=0; i<row_width; i++) { *(--dp)=(byte)(255-*(--sp)); *(--dp)=(byte)(255-*(--sp)); sp-=6; dp=sp; } } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA) { // This inverts the alpha channel in GA if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { *(--dp)=(byte)(255-*(--sp)); *(--dp)=*(--sp); } } else // This inverts the alpha channel in GGAA { for(uint i=0; i<row_width; i++) { *(--dp)=(byte)(255-*(--sp)); *(--dp)=(byte)(255-*(--sp)); sp-=2; dp=sp; } } } } }
static unsafe void png_do_read_swap_alpha(ref png_row_info row_info, byte[] row) { uint row_width=row_info.width; fixed(byte* row_=row) { byte* sp=row_+row_info.rowbytes+1; // skip filter value byte* dp=row_+row_info.rowbytes+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { // This converts from RGBA to ARGB if(row_info.bit_depth==8) { byte save; for(uint i=0; i<row_width; i++) { save=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=save; } } else // This converts from RRGGBBAA to AARRGGBB { byte save0, save1; for(uint i=0; i<row_width; i++) { save0=*(--sp); save1=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=save0; *(--dp)=save1; } } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA) { // This converts from GA to AG if(row_info.bit_depth==8) { byte save; for(uint i=0; i<row_width; i++) { save=*(--sp); *(--dp)=*(--sp); *(--dp)=save; } } else // This converts from GGAA to AAGG { byte save0, save1; for(uint i=0; i<row_width; i++) { save0=*(--sp); save1=*(--sp); *(--dp)=*(--sp); *(--dp)=*(--sp); *(--dp)=save0; *(--dp)=save1; } } } } }
// chop rows of bit depth 16 down to 8 static unsafe void png_do_chop(ref png_row_info row_info, byte[] row) { if(row_info.bit_depth!=16) return; uint istop=row_info.width*row_info.channels; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value for(uint i=0; i<istop; i++, sp+=2, dp++) *dp=*sp; } row_info.bit_depth=8; row_info.pixel_depth=(byte)(8*row_info.channels); row_info.rowbytes=row_info.width*row_info.channels; }
// Reverse the effects of png_do_shift. This routine merely shifts the // pixels back to their significant bits values. Thus, if you have // a row of bit depth 8, but only 5 are significant, this will shift // the values back to 0 through 31. static unsafe void png_do_unshift(ref png_row_info row_info, byte[] row, ref png_color_8 sig_bits) { if(row_info.color_type==PNG_COLOR_TYPE.PALETTE) return; int[] shift=new int[4]; uint channels=0; ushort value=0; uint row_width=row_info.width; if((row_info.color_type&PNG_COLOR_TYPE.COLOR_MASK)==PNG_COLOR_TYPE.COLOR_MASK) { shift[channels++]=row_info.bit_depth-sig_bits.red; shift[channels++]=row_info.bit_depth-sig_bits.green; shift[channels++]=row_info.bit_depth-sig_bits.blue; } else shift[channels++]=row_info.bit_depth-sig_bits.gray; if((row_info.color_type&PNG_COLOR_TYPE.ALPHA_MASK)==PNG_COLOR_TYPE.ALPHA_MASK) shift[channels++]=row_info.bit_depth-sig_bits.alpha; for(uint c=0; c<channels; c++) { if(shift[c]<=0) shift[c]=0; else value=1; } if(value==0) return; fixed(byte* row_=row) { byte* bp=row_+1; // skip filter value switch(row_info.bit_depth) { case 2: { uint istop=row_info.rowbytes; for(uint i=0; i<istop; i++) { *bp>>=1; *bp++&=0x55; } } break; case 4: { uint istop=row_info.rowbytes; byte mask=(byte)((((int)0xf0>>shift[0])&(int)0xf0)|(byte)((int)0xf>>shift[0])); for(uint i=0; i<istop; i++) { *bp>>=shift[0]; *bp++&=mask; } } break; case 8: { uint istop=row_width*channels; for(uint i=0; i<istop; i++) *bp++>>=shift[i%channels]; } break; case 16: { uint istop=row_width*channels; for(uint i=0; i<istop; i++) { value=(ushort)((*bp<<8)+*(bp+1)); value>>=shift[i%channels]; *bp++=(byte)(value>>8); *bp++=(byte)(value&0xff); } } break; } } }
// Unpack pixels of 1, 2, or 4 bits per pixel into 1 byte per pixel, // without changing the actual values. Thus, if you had a row with // a bit depth of 1, you would end up with bytes that only contained // the numbers 0 or 1. If you would rather they contain 0 and 255, use // png_do_shift() after this. static unsafe void png_do_unpack(ref png_row_info row_info, byte[] row) { if(row_info.bit_depth>=8) return; uint row_width=row_info.width; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value dp+=row_width-1; switch(row_info.bit_depth) { case 1: { sp+=(row_width-1)>>3; int shift=7-(int)((row_width+7)&0x07); for(uint i=0; i<row_width; i++) { *dp=(byte)((*sp>>shift)&0x01); if(shift==7) { shift=0; sp--; } else shift++; dp--; } } break; case 2: { sp+=(row_width-1)>>2; int shift=(int)((3-((row_width+3)&0x03))<<1); for(uint i=0; i<row_width; i++) { *dp=(byte)((*sp>>shift)&0x03); if(shift==6) { shift=0; sp--; } else shift+=2; dp--; } } break; case 4: { sp+=(row_width-1)>>1; int shift=(int)((1-((row_width+1)&0x01))<<2); for(uint i=0; i<row_width; i++) { *dp=(byte)((*sp>>shift)&0x0f); if(shift==4) { shift=0; sp--; } else shift=4; dp--; } } break; } } row_info.bit_depth=8; row_info.pixel_depth=(byte)(8*row_info.channels); row_info.rowbytes=row_width*row_info.channels; }
// swaps red and blue bytes within a pixel static unsafe void png_do_bgr(ref png_row_info row_info, byte[] row) { fixed(byte* row_=row) { byte* rp=row_+1;// skip filter value if((row_info.color_type&PNG_COLOR_TYPE.COLOR_MASK)==PNG_COLOR_TYPE.COLOR_MASK) { uint row_width=row_info.width; if(row_info.bit_depth==8) { if(row_info.color_type==PNG_COLOR_TYPE.RGB) { for(uint i=0; i<row_width; i++, rp+=3) { byte save=*rp; *rp=*(rp+2); *(rp+2)=save; } } else if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { for(uint i=0; i<row_width; i++, rp+=4) { byte save=*rp; *rp=*(rp+2); *(rp+2)=save; } } } else if(row_info.bit_depth==16) { if(row_info.color_type==PNG_COLOR_TYPE.RGB) { for(uint i=0; i<row_width; i++, rp+=6) { byte save=*rp; *rp=*(rp+4); *(rp+4)=save; save=*(rp+1); *(rp+1)=*(rp+5); *(rp+5)=save; } } else if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { for(uint i=0; i<row_width; i++, rp+=8) { byte save=*rp; *rp=*(rp+4); *(rp+4)=save; save=*(rp+1); *(rp+1)=*(rp+5); *(rp+5)=save; } } } } } }
static unsafe void png_do_quantize(ref png_row_info row_info, byte[] row, byte[] palette_lookup, byte[] quantize_lookup) { uint row_width=row_info.width; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.RGB&&palette_lookup!=null&&row_info.bit_depth==8) { int r, g, b, p; for(uint i=0; i<row_width; i++) { r=*sp++; g=*sp++; b=*sp++; // this looks real messy, but the compiler will reduce // it down to a reasonable formula. For example, with // 5 bits per color, we get: // p=(((r>>3)&0x1f)<<10)|(((g>>3)&0x1f)<<5)|((b>>3)&0x1f); p=(((r>>(8-PNG_QUANTIZE_RED_BITS))&((1<<PNG_QUANTIZE_RED_BITS)-1))<<(PNG_QUANTIZE_GREEN_BITS+PNG_QUANTIZE_BLUE_BITS))| (((g>>(8-PNG_QUANTIZE_GREEN_BITS))&((1<<PNG_QUANTIZE_GREEN_BITS)-1))<<(PNG_QUANTIZE_BLUE_BITS))| ((b>>(8-PNG_QUANTIZE_BLUE_BITS))&((1<<PNG_QUANTIZE_BLUE_BITS)-1)); *dp++=palette_lookup[p]; } row_info.color_type=PNG_COLOR_TYPE.PALETTE; row_info.channels=1; row_info.pixel_depth=row_info.bit_depth; row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); } else if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA&&palette_lookup!=null&&row_info.bit_depth==8) { int r, g, b, p; for(uint i=0; i<row_width; i++) { r=*sp++; g=*sp++; b=*sp++; sp++; p=(((r>>(8-PNG_QUANTIZE_RED_BITS))&((1<<PNG_QUANTIZE_RED_BITS)-1))<<(PNG_QUANTIZE_GREEN_BITS+PNG_QUANTIZE_BLUE_BITS))| (((g>>(8-PNG_QUANTIZE_GREEN_BITS))&((1<<PNG_QUANTIZE_GREEN_BITS)-1))<<(PNG_QUANTIZE_BLUE_BITS))| ((b>>(8-PNG_QUANTIZE_BLUE_BITS))&((1<<PNG_QUANTIZE_BLUE_BITS)-1)); *dp++=palette_lookup[p]; } row_info.color_type=PNG_COLOR_TYPE.PALETTE; row_info.channels=1; row_info.pixel_depth=row_info.bit_depth; row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); } else if(row_info.color_type==PNG_COLOR_TYPE.PALETTE&&quantize_lookup!=null&&row_info.bit_depth==8) { for(uint i=0; i<row_width; i++, sp++) *sp=quantize_lookup[*sp]; } } }
// remove filler or alpha byte(s) static unsafe void png_do_strip_filler(ref png_row_info row_info, byte[] row, PNG_FLAG flags) { fixed(byte *row_ = row) { byte *sp = row_ + 1; // skip filter value byte *dp = row_ + 1; // skip filter value uint row_width = row_info.width; uint i; if (row_info.channels == 4 && (row_info.color_type == PNG_COLOR_TYPE.RGB || (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA && (flags & PNG_FLAG.STRIP_ALPHA) == PNG_FLAG.STRIP_ALPHA))) { if (row_info.bit_depth == 8) { // This converts from RGBX or RGBA to RGB if ((flags & PNG_FLAG.FILLER_AFTER) == PNG_FLAG.FILLER_AFTER) { dp += 3; sp += 4; for (i = 1; i < row_width; i++) { *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; sp++; } } // This converts from XRGB or ARGB to RGB else { for (i = 0; i < row_width; i++) { sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; } } row_info.pixel_depth = 24; row_info.rowbytes = row_width * 3; } else // if(row_info->bit_depth==16) { if ((flags & PNG_FLAG.FILLER_AFTER) == PNG_FLAG.FILLER_AFTER) { // This converts from RRGGBBXX or RRGGBBAA to RRGGBB sp += 8; dp += 6; for (i = 1; i < row_width; i++) { *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; sp += 2; } } else { // This converts from XXRRGGBB or AARRGGBB to RRGGBB for (i = 0; i < row_width; i++) { sp += 2; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; } } row_info.pixel_depth = 48; row_info.rowbytes = row_width * 6; } row_info.channels = 3; } else if (row_info.channels == 2 && (row_info.color_type == PNG_COLOR_TYPE.GRAY || (row_info.color_type == PNG_COLOR_TYPE.GRAY_ALPHA && (flags & PNG_FLAG.STRIP_ALPHA) == PNG_FLAG.STRIP_ALPHA))) { if (row_info.bit_depth == 8) { // This converts from GX or GA to G if ((flags & PNG_FLAG.FILLER_AFTER) == PNG_FLAG.FILLER_AFTER) { for (i = 0; i < row_width; i++) { *dp++ = *sp++; sp++; } } // This converts from XG or AG to G else { for (i = 0; i < row_width; i++) { sp++; *dp++ = *sp++; } } row_info.pixel_depth = 8; row_info.rowbytes = row_width; } else // if(row_info->bit_depth==16) { if ((flags & PNG_FLAG.FILLER_AFTER) == PNG_FLAG.FILLER_AFTER) { // This converts from GGXX or GGAA to GG sp += 4; dp += 2; for (i = 1; i < row_width; i++) { *dp++ = *sp++; *dp++ = *sp++; sp += 2; } } else { // This converts from XXGG or AAGG to GG for (i = 0; i < row_width; i++) { sp += 2; *dp++ = *sp++; *dp++ = *sp++; } } row_info.pixel_depth = 16; row_info.rowbytes = row_width * 2; } row_info.channels = 1; } } if ((flags & PNG_FLAG.STRIP_ALPHA) == PNG_FLAG.STRIP_ALPHA) { row_info.color_type &= ~PNG_COLOR_TYPE.ALPHA_MASK; } }
// Pack pixels into bytes. Pass the true bit depth in bit_depth. The // row_info bit depth should be 8 (one pixel per byte). The channels // should be 1 (this only happens on grayscale and paletted images). unsafe void png_do_pack(ref png_row_info row_info, byte[] row, uint bit_depth) { if (row_info.bit_depth != 8 || row_info.channels != 1) { return; fixed(byte *row_ = row) { byte *sp = row_ + 1, dp = row_ + 1; // skip filter value uint row_width = row_info.width; int v = 0; switch ((int)bit_depth) { case 1: { int mask = 0x80; for (uint i = 0; i < row_width; i++) { if (*sp != 0) { v |= mask; } sp++; if (mask > 1) { mask >>= 1; } else { mask = 0x80; *dp = (byte)v; dp++; v = 0; } } if (mask != 0x80) { *dp = (byte)v; } } break; case 2: { int shift = 6; for (uint i = 0; i < row_width; i++) { byte value = (byte)(*sp & 0x03); v |= (value << shift); if (shift == 0) { shift = 6; *dp = (byte)v; dp++; v = 0; } else { shift -= 2; } sp++; } if (shift != 6) { *dp = (byte)v; } } break; case 4: { int shift = 4; for (uint i = 0; i < row_width; i++) { byte value = (byte)(*sp & 0x0f); v |= (value << shift); if (shift == 0) { shift = 4; *dp = (byte)v; dp++; v = 0; } else { shift -= 4; } sp++; } if (shift != 4) { *dp = (byte)v; } } break; } row_info.bit_depth = (byte)bit_depth; row_info.pixel_depth = (byte)(bit_depth * row_info.channels); row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); } } // Shift pixel values to take advantage of whole range. Pass the // true number of bits in bit_depth. The row should be packed // according to row_info->bit_depth. Thus, if you had a row of // bit depth 4, but the pixels only had values from 0 to 7, you // would pass 3 as bit_depth, and this routine would translate the // data to 0 to 15. unsafe void png_do_shift(png_row_info row_info, byte[] row, png_color_8 bit_depth) { if (row_info.color_type == PNG_COLOR_TYPE.PALETTE) return; } int[] shift_start = new int[4], shift_dec = new int[4]; uint channels = 0; if ((row_info.color_type & PNG_COLOR_TYPE.COLOR_MASK) == PNG_COLOR_TYPE.COLOR_MASK) { shift_start[channels] = row_info.bit_depth - bit_depth.red; shift_dec[channels] = bit_depth.red; channels++; shift_start[channels] = row_info.bit_depth - bit_depth.green; shift_dec[channels] = bit_depth.green; channels++; shift_start[channels] = row_info.bit_depth - bit_depth.blue; shift_dec[channels] = bit_depth.blue; channels++; } else { shift_start[channels] = row_info.bit_depth - bit_depth.gray; shift_dec[channels] = bit_depth.gray; channels++; } if ((row_info.color_type & PNG_COLOR_TYPE.ALPHA_MASK) == PNG_COLOR_TYPE.ALPHA_MASK) { shift_start[channels] = row_info.bit_depth - bit_depth.alpha; shift_dec[channels] = bit_depth.alpha; channels++; } fixed(byte *row_ = row) { // with low row depths, could only be grayscale, so one channel byte *bp = row_ + 1; // skip filter value if (row_info.bit_depth < 8) { byte mask; uint row_bytes = row_info.rowbytes; if (bit_depth.gray == 1 && row_info.bit_depth == 2) { mask = 0x55; } else if (row_info.bit_depth == 4 && bit_depth.gray == 3) { mask = 0x11; } else { mask = 0xff; } for (uint i = 0; i < row_bytes; i++, bp++) { ushort v = *bp; * bp = 0; for (int j = shift_start[0]; j > -shift_dec[0]; j -= shift_dec[0]) { if (j > 0) { *bp |= (byte)((v << j) & 0xff); } else { *bp |= (byte)((v >> (-j)) & mask); } } } } else if (row_info.bit_depth == 8) { uint istop = channels * row_info.width; for (uint i = 0; i < istop; i++, bp++) { int c = (int)(i % channels); ushort v = *bp; * bp = 0; for (int j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) { if (j > 0) { *bp |= (byte)((v << j) & 0xff); } else { *bp |= (byte)((v >> (-j)) & 0xff); } } } } else { uint istop = channels * row_info.width; for (uint i = 0; i < istop; i++) { int c = (int)(i % channels); ushort v = (ushort)(((ushort)(*bp) << 8) + *(bp + 1)); ushort value = 0; for (int j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) { if (j > 0) { value |= (ushort)((v << j) & (ushort)0xffff); } else { value |= (ushort)((v >> (-j)) & (ushort)0xffff); } } *bp++ = (byte)(value >> 8); *bp++ = (byte)(value & 0xff); } } } } unsafe void png_do_write_swap_alpha(png_row_info row_info, byte[] row) { fixed(byte *row_ = row) { byte *sp = row_ + 1, dp = row_ + 1; // skip filter value uint row_width = row_info.width; if (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA) { // This converts from ARGB to RGBA if (row_info.bit_depth == 8) { for (uint i = 0; i < row_width; i++) { byte save = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = save; } } else // This converts from AARRGGBB to RRGGBBAA { for (uint i = 0; i < row_width; i++) { byte save0, save1; save0 = *(sp++); save1 = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = save0; *(dp++) = save1; } } } else if (row_info.color_type == PNG_COLOR_TYPE.GRAY_ALPHA) { // This converts from AG to GA if (row_info.bit_depth == 8) { for (uint i = 0; i < row_width; i++) { byte save = *(sp++); *(dp++) = *(sp++); *(dp++) = save; } } else // This converts from AAGG to GGAA { for (uint i = 0; i < row_width; i++) { byte save0, save1; save0 = *(sp++); save1 = *(sp++); *(dp++) = *(sp++); *(dp++) = *(sp++); *(dp++) = save0; *(dp++) = save1; } } } } } unsafe void png_do_write_invert_alpha(png_row_info row_info, byte[] row) { fixed(byte *row_ = row) { byte *sp = row_ + 1, dp = row_ + 1; // skip filter value uint row_width = row_info.width; if (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA) { // This inverts the alpha channel in RGBA if (row_info.bit_depth == 8) { for (uint i = 0; i < row_width; i++) { sp += 3; dp = sp; *(dp++) = (byte)(255 - *(sp++)); } } else // This inverts the alpha channel in RRGGBBAA { for (uint i = 0; i < row_width; i++) { sp += 6; dp = sp; *(dp++) = (byte)(255 - *(sp++)); *(dp++) = (byte)(255 - *(sp++)); } } } else if (row_info.color_type == PNG_COLOR_TYPE.GRAY_ALPHA) { // This inverts the alpha channel in GA if (row_info.bit_depth == 8) { for (uint i = 0; i < row_width; i++) { *(dp++) = *(sp++); *(dp++) = (byte)(255 - *(sp++)); } } else // This inverts the alpha channel in GGAA { for (uint i = 0; i < row_width; i++) { sp += 2; dp = sp; *(dp++) = (byte)(255 - *(sp++)); *(dp++) = (byte)(255 - *(sp++)); } } } } } // undoes intrapixel differencing unsafe void png_do_write_intrapixel(png_row_info row_info, byte[] row) { if ((row_info.color_type & PNG_COLOR_TYPE.COLOR_MASK) != PNG_COLOR_TYPE.COLOR_MASK) return; fixed(byte *row_ = row) { byte *rp = row_ + 1; // skip filter value int bytes_per_pixel; uint row_width = row_info.width; if (row_info.bit_depth == 8) { if (row_info.color_type == PNG_COLOR_TYPE.RGB) { bytes_per_pixel = 3; } else if (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA) { bytes_per_pixel = 4; } else { return; } for (uint i = 0; i < row_width; i++, rp += bytes_per_pixel) { *(rp) = (byte)((*rp - *(rp + 1)) & 0xff); *(rp + 2) = (byte)((*(rp + 2) - *(rp + 1)) & 0xff); } } else if (row_info.bit_depth == 16) { if (row_info.color_type == PNG_COLOR_TYPE.RGB) { bytes_per_pixel = 6; } else if (row_info.color_type == PNG_COLOR_TYPE.RGB_ALPHA) { bytes_per_pixel = 8; } else { return; } for (uint i = 0; i < row_width; i++, rp += bytes_per_pixel) { uint s0 = (uint)(*(rp) << 8) | *(rp + 1); uint s1 = (uint)(*(rp + 2) << 8) | *(rp + 3); uint s2 = (uint)(*(rp + 4) << 8) | *(rp + 5); uint red = (uint)((s0 - s1) & 0xffffL); uint blue = (uint)((s2 - s1) & 0xffffL); *(rp) = (byte)((red >> 8) & 0xff); *(rp + 1) = (byte)(red & 0xff); *(rp + 4) = (byte)((blue >> 8) & 0xff); *(rp + 5) = (byte)(blue & 0xff); } } } } } }
// expand grayscale files to RGB, with or without alpha static unsafe void png_do_gray_to_rgb(ref png_row_info row_info, byte[] row) { uint i; uint row_width=row_info.width; if(!(row_info.bit_depth>=8&&(row_info.color_type&PNG_COLOR_TYPE.COLOR_MASK)!=PNG_COLOR_TYPE.COLOR_MASK)) return; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.GRAY) { if(row_info.bit_depth==8) { sp+=row_width-1; byte* dp=sp+row_width*2; for(i=0; i<row_width; i++) { *(dp--)=*sp; *(dp--)=*sp; *(dp--)=*(sp--); } } else { sp+=row_width*2-1; byte* dp=sp+row_width*4; for(i=0; i<row_width; i++) { *(dp--)=*sp; *(dp--)=*(sp-1); *(dp--)=*sp; *(dp--)=*(sp-1); *(dp--)=*(sp--); *(dp--)=*(sp--); } } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA) { if(row_info.bit_depth==8) { sp+=row_width*2-1; byte* dp=sp+row_width*2; for(i=0; i<row_width; i++) { *(dp--)=*(sp--); *(dp--)=*sp; *(dp--)=*sp; *(dp--)=*(sp--); } } else { sp+=row_width*4-1; byte* dp=sp+row_width*4; for(i=0; i<row_width; i++) { *(dp--)=*(sp--); *(dp--)=*(sp--); *(dp--)=*sp; *(dp--)=*(sp-1); *(dp--)=*sp; *(dp--)=*(sp-1); *(dp--)=*(sp--); *(dp--)=*(sp--); } } } } row_info.channels+=(byte)2; row_info.color_type|=PNG_COLOR_TYPE.COLOR_MASK; row_info.pixel_depth=(byte)(row_info.channels*row_info.bit_depth); row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); }
// swaps pixel packing order within bytes static unsafe void png_do_packswap(ref png_row_info row_info, byte[] row) { if(row_info.bit_depth<8) { byte[] table; if(row_info.bit_depth==1) table=onebppswaptable; else if(row_info.bit_depth==2) table=twobppswaptable; else if(row_info.bit_depth==4) table=fourbppswaptable; else return; fixed(byte* row_=row) { byte* rp=row_+1;// skip filter value byte* end=rp+row_info.rowbytes; for(; rp<end; rp++) *rp=table[*rp]; } } }
// reduce RGB files to grayscale, with or without alpha // using the equation given in Poynton's ColorFAQ at // <http://www.inforamp.net/~poynton/> (THIS LINK IS DEAD June 2008) // New link: // <http://www.poynton.com/notes/colour_and_gamma/> // Charles Poynton poynton at poynton.com // // Y=0.212671*R+0.715160*G+0.072169*B // // We approximate this with // // Y=0.21268*R+0.7151*G+0.07217*B // // which can be expressed with integers as // // Y=(6969*R+23434*G+2365*B)/32768 // // The calculation is to be done in a linear colorspace. // // Other integer coefficents can be used via png_set_rgb_to_gray(). unsafe bool png_do_rgb_to_gray(ref png_row_info row_info, byte[] row) { uint row_width=row_info.width; bool rgb_error=false; if((row_info.color_type&PNG_COLOR_TYPE.COLOR_MASK)!=PNG_COLOR_TYPE.COLOR_MASK) return rgb_error; uint rc=rgb_to_gray_red_coeff; uint gc=rgb_to_gray_green_coeff; uint bc=rgb_to_gray_blue_coeff; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.RGB) { if(row_info.bit_depth==8) { if(gamma_from_1!=null&&gamma_to_1!=null) { for(uint i=0; i<row_width; i++) { byte red=gamma_to_1[*(sp++)]; byte green=gamma_to_1[*(sp++)]; byte blue=gamma_to_1[*(sp++)]; if(red!=green||red!=blue) { rgb_error=true; *(dp++)=gamma_from_1[(rc*red+gc*green+bc*blue)>>15]; } else *(dp++)=*(sp-1); } } else { for(uint i=0; i<row_width; i++) { byte red=*(sp++); byte green=*(sp++); byte blue=*(sp++); if(red!=green||red!=blue) { rgb_error=true; *(dp++)=(byte)((rc*red+gc*green+bc*blue)>>15); } else *(dp++)=*(sp-1); } } } else // RGB bit_depth==16 { if(gamma_16_to_1!=null&&gamma_16_from_1!=null) { for(uint i=0; i<row_width; i++) { ushort red, green, blue, w; red=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; green=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; blue=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; if(red==green&&red==blue) w=red; else { ushort red_1=gamma_16_to_1[(red&0xff)>>gamma_shift][red>>8]; ushort green_1=gamma_16_to_1[(green&0xff)>>gamma_shift][green>>8]; ushort blue_1=gamma_16_to_1[(blue&0xff)>>gamma_shift][blue>>8]; ushort gray16=(ushort)((rc*red_1+gc*green_1+bc*blue_1)>>15); w=gamma_16_from_1[(gray16&0xff)>>gamma_shift][gray16>>8]; rgb_error=true; } *(dp++)=(byte)((w>>8)&0xff); *(dp++)=(byte)(w&0xff); } } else { for(uint i=0; i<row_width; i++) { ushort red, green, blue, gray16; red=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; green=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; blue=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; if(red!=green||red!=blue) rgb_error=true; gray16=(ushort)((rc*red+gc*green+bc*blue)>>15); *(dp++)=(byte)((gray16>>8)&0xff); *(dp++)=(byte)(gray16&0xff); } } } } else if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { if(row_info.bit_depth==8) { if(gamma_from_1!=null&&gamma_to_1!=null) { for(uint i=0; i<row_width; i++) { byte red=gamma_to_1[*(sp++)]; byte green=gamma_to_1[*(sp++)]; byte blue=gamma_to_1[*(sp++)]; if(red!=green||red!=blue) rgb_error=true; *(dp++)=gamma_from_1[(rc*red+gc*green+bc*blue)>>15]; *(dp++)=*(sp++); // alpha } } else { for(uint i=0; i<row_width; i++) { byte red=*(sp++); byte green=*(sp++); byte blue=*(sp++); if(red!=green||red!=blue) rgb_error=true; *(dp++)=(byte)((rc*red+gc*green+bc*blue)>>15); *(dp++)=*(sp++); // alpha } } } else // RGBA bit_depth==16 { if(gamma_16_to_1!=null&&gamma_16_from_1!=null) { for(uint i=0; i<row_width; i++) { ushort red, green, blue, w; red=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; green=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; blue=(ushort)(((*(sp))<<8)|*(sp+1)); sp+=2; if(red==green&&red==blue) w=red; else { ushort red_1=gamma_16_to_1[(red&0xff)>>gamma_shift][red>>8]; ushort green_1=gamma_16_to_1[(green&0xff)>>gamma_shift][green>>8]; ushort blue_1=gamma_16_to_1[(blue&0xff)>>gamma_shift][blue>>8]; ushort gray16=(ushort)((rc*red_1+gc*green_1+bc*blue_1)>>15); w=gamma_16_from_1[(gray16&0xff)>>gamma_shift][gray16>>8]; rgb_error=true; } *(dp++)=(byte)((w>>8)&0xff); *(dp++)=(byte)(w&0xff); *(dp++)=*(sp++); // alpha *(dp++)=*(sp++); } } else { for(uint i=0; i<row_width; i++) { ushort red, green, blue, gray16; red=(ushort)((*(sp)<<8)|*(sp+1)); sp+=2; green=(ushort)((*(sp)<<8)|*(sp+1)); sp+=2; blue=(ushort)((*(sp)<<8)|*(sp+1)); sp+=2; if(red!=green||red!=blue) rgb_error=true; gray16=(ushort)((rc*red+gc*green+bc*blue)>>15); *(dp++)=(byte)((gray16>>8)&0xff); *(dp++)=(byte)(gray16&0xff); *(dp++)=*(sp++); // alpha *(dp++)=*(sp++); } } } } } row_info.channels-=(byte)2; row_info.color_type&=~PNG_COLOR_TYPE.COLOR_MASK; row_info.pixel_depth=(byte)(row_info.channels*row_info.bit_depth); row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); return rgb_error; }
// Replace any alpha or transparency with the supplied background color. // "background" is already in the screen gamma, while "background_1" is // at a gamma of 1.0. Paletted files have already been taken care of. static unsafe void png_do_background(ref png_row_info row_info, byte[] row, ref png_color_16 trans_color, ref png_color_16 background, ref png_color_16 background_1, byte[] gamma_table, byte[] gamma_from_1, byte[] gamma_to_1, ushort[][] gamma_16, ushort[][] gamma_16_from_1, ushort[][] gamma_16_to_1, int gamma_shift) { uint i; uint row_width=row_info.width; int shift; if(!((row_info.color_type&PNG_COLOR_TYPE.ALPHA_MASK)!=PNG_COLOR_TYPE.ALPHA_MASK|| row_info.color_type!=PNG_COLOR_TYPE.PALETTE)) return; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value switch(row_info.color_type) { case PNG_COLOR_TYPE.GRAY: { switch(row_info.bit_depth) { case 1: { shift=7; for(i=0; i<row_width; i++) { if((ushort)((*sp>>shift)&0x01)==trans_color.gray) { *sp&=(byte)((0x7f7f>>(7-shift))&0xff); *sp|=(byte)(background.gray<<shift); } if(shift==0) { shift=7; sp++; } else shift--; } } break; case 2: { shift=6; if(gamma_table!=null) { for(i=0; i<row_width; i++) { if((ushort)((*sp>>shift)&0x03)==trans_color.gray) { *sp&=(byte)((0x3f3f>>(6-shift))&0xff); *sp|=(byte)(background.gray<<shift); } else { byte p=(byte)((*sp>>shift)&0x03); byte g=(byte)((gamma_table[p|(p<<2)|(p<<4)|(p<<6)]>>6)&0x03); *sp&=(byte)((0x3f3f>>(6-shift))&0xff); *sp|=(byte)(g<<shift); } if(shift==0) { shift=6; sp++; } else shift-=2; } } else { for(i=0; i<row_width; i++) { if((ushort)((*sp>>shift)&0x03)==trans_color.gray) { *sp&=(byte)((0x3f3f>>(6-shift))&0xff); *sp|=(byte)(background.gray<<shift); } if(shift==0) { shift=6; sp++; } else shift-=2; } } } break; case 4: { shift=4; if(gamma_table!=null) { for(i=0; i<row_width; i++) { if((ushort)((*sp>>shift)&0x0f)==trans_color.gray) { *sp&=(byte)((0xf0f>>(4-shift))&0xff); *sp|=(byte)(background.gray<<shift); } else { byte p=(byte)((*sp>>shift)&0x0f); byte g=(byte)((gamma_table[p|(p<<4)]>>4)&0x0f); *sp&=(byte)((0xf0f>>(4-shift))&0xff); *sp|=(byte)(g<<shift); } if(shift==0) { shift=4; sp++; } else shift-=4; } } else { for(i=0; i<row_width; i++) { if((ushort)((*sp>>shift)&0x0f)==trans_color.gray) { *sp&=(byte)((0xf0f>>(4-shift))&0xff); *sp|=(byte)(background.gray<<shift); } if(shift==0) { shift=4; sp++; } else shift-=4; } } } break; case 8: { if(gamma_table!=null) { for(i=0; i<row_width; i++, sp++) { if(*sp==trans_color.gray) *sp=(byte)background.gray; else *sp=gamma_table[*sp]; } } else { for(i=0; i<row_width; i++, sp++) { if(*sp==trans_color.gray) *sp=(byte)background.gray; } } break; } case 16: { if(gamma_16!=null) { for(i=0; i<row_width; i++, sp+=2) { ushort v=(ushort)(((*sp)<<8)+*(sp+1)); if(v==trans_color.gray) { // background is already in screen gamma *sp=(byte)((background.gray>>8)&0xff); *(sp+1)=(byte)(background.gray&0xff); } else { v=gamma_16[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); } } } else { for(i=0; i<row_width; i++, sp+=2) { ushort v=(ushort)(((*sp)<<8)+*(sp+1)); if(v==trans_color.gray) { *sp=(byte)((background.gray>>8)&0xff); *(sp+1)=(byte)(background.gray&0xff); } } } } break; } // switch(row_info->bit_depth) } break; case PNG_COLOR_TYPE.RGB: { if(row_info.bit_depth==8) { if(gamma_table!=null) { for(i=0; i<row_width; i++, sp+=3) { if(*sp==trans_color.red&&*(sp+1)==trans_color.green&&*(sp+2)==trans_color.blue) { *sp=(byte)background.red; *(sp+1)=(byte)background.green; *(sp+2)=(byte)background.blue; } else { *sp=gamma_table[*sp]; *(sp+1)=gamma_table[*(sp+1)]; *(sp+2)=gamma_table[*(sp+2)]; } } } else { for(i=0; i<row_width; i++, sp+=3) { if(*sp==trans_color.red&&*(sp+1)==trans_color.green&&*(sp+2)==trans_color.blue) { *sp=(byte)background.red; *(sp+1)=(byte)background.green; *(sp+2)=(byte)background.blue; } } } } else // if(row_info->bit_depth==16) { if(gamma_16!=null) { for(i=0; i<row_width; i++, sp+=6) { ushort r=(ushort)(((*sp)<<8)+*(sp+1)); ushort g=(ushort)(((*(sp+2))<<8)+*(sp+3)); ushort b=(ushort)(((*(sp+4))<<8)+*(sp+5)); if(r==trans_color.red&&g==trans_color.green&&b==trans_color.blue) { // background is already in screen gamma *sp=(byte)((background.red>>8)&0xff); *(sp+1)=(byte)(background.red&0xff); *(sp+2)=(byte)((background.green>>8)&0xff); *(sp+3)=(byte)(background.green&0xff); *(sp+4)=(byte)((background.blue>>8)&0xff); *(sp+5)=(byte)(background.blue&0xff); } else { ushort v=gamma_16[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); v=gamma_16[*(sp+3)>>gamma_shift][*(sp+2)]; *(sp+2)=(byte)((v>>8)&0xff); *(sp+3)=(byte)(v&0xff); v=gamma_16[*(sp+5)>>gamma_shift][*(sp+4)]; *(sp+4)=(byte)((v>>8)&0xff); *(sp+5)=(byte)(v&0xff); } } } else { for(i=0; i<row_width; i++, sp+=6) { ushort r=(ushort)(((*sp)<<8)+*(sp+1)); ushort g=(ushort)(((*(sp+2))<<8)+*(sp+3)); ushort b=(ushort)(((*(sp+4))<<8)+*(sp+5)); if(r==trans_color.red&&g==trans_color.green&&b==trans_color.blue) { *sp=(byte)((background.red>>8)&0xff); *(sp+1)=(byte)(background.red&0xff); *(sp+2)=(byte)((background.green>>8)&0xff); *(sp+3)=(byte)(background.green&0xff); *(sp+4)=(byte)((background.blue>>8)&0xff); *(sp+5)=(byte)(background.blue&0xff); } } } } } break; case PNG_COLOR_TYPE.GRAY_ALPHA: { if(row_info.bit_depth==8) { if(gamma_to_1!=null&&gamma_from_1!=null&&gamma_table!=null) { for(i=0; i<row_width; i++, sp+=2, dp++) { ushort a=*(sp+1); if(a==0xff) *dp=gamma_table[*sp]; else if(a==0) *dp=(byte)background.gray; // background is already in screen gamma else { byte w, v=gamma_to_1[*sp]; //png_composite(w, v, a, background_1->gray); w=(byte)(((ushort)v*a+background_1.gray*(ushort)(255-a)+(ushort)127)/255); *dp=gamma_from_1[w]; } } } else { for(i=0; i<row_width; i++, sp+=2, dp++) { byte a=*(sp+1); if(a==0xff) *dp=*sp; else if(a==0) *dp=(byte)background.gray; else //png_composite(*dp, *sp, a, background_1->gray); *dp=(byte)(((ushort)(*sp)*a+background_1.gray*(ushort)(255-a)+(ushort)127)/255); } } } else // if(png_ptr->bit_depth==16) { if(gamma_16!=null&&gamma_16_from_1!=null&&gamma_16_to_1!=null) { for(i=0; i<row_width; i++, sp+=4, dp+=2) { ushort a=(ushort)(((*(sp+2))<<8)+*(sp+3)); if(a==(ushort)0xffff) { ushort v=gamma_16[*(sp+1)>>gamma_shift][*sp]; *dp=(byte)((v>>8)&0xff); *(dp+1)=(byte)(v&0xff); } else if(a==0) { // background is already in screen gamma *dp=(byte)((background.gray>>8)&0xff); *(dp+1)=(byte)(background.gray&0xff); } else { ushort v, w, g=gamma_16_to_1[*(sp+1)>>gamma_shift][*sp]; //png_composite_16(v, g, a, background_1->gray); v=(ushort)(((uint)g*(uint)a+(uint)background_1.gray*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); w=gamma_16_from_1[(v&0xff)>>gamma_shift][v>>8]; *dp=(byte)((w>>8)&0xff); *(dp+1)=(byte)(w&0xff); } } } else { for(i=0; i<row_width; i++, sp+=4, dp+=2) { ushort a=(ushort)(((*(sp+2))<<8)+*(sp+3)); if(a==(ushort)0xffff) { //memcpy(dp, sp, 2); *dp=*sp; *(dp+1)=*(sp+1); } else if(a==0) { *dp=(byte)((background.gray>>8)&0xff); *(dp+1)=(byte)(background.gray&0xff); } else { ushort v, g=(ushort)(((*sp)<<8)+*(sp+1)); //png_composite_16(v, g, a, background_1->gray); v=(ushort)(((uint)g*(uint)a+(uint)(background_1.gray)*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); *dp=(byte)((v>>8)&0xff); *(dp+1)=(byte)(v&0xff); } } } } } break; case PNG_COLOR_TYPE.RGB_ALPHA: { if(row_info.bit_depth==8) { if(gamma_to_1!=null&&gamma_from_1!=null&&gamma_table!=null) { for(i=0; i<row_width; i++, sp+=4, dp+=3) { byte a=*(sp+3); if(a==0xff) { *dp=gamma_table[*sp]; *(dp+1)=gamma_table[*(sp+1)]; *(dp+2)=gamma_table[*(sp+2)]; } else if(a==0) { // background is already in screen gamma *dp=(byte)background.red; *(dp+1)=(byte)background.green; *(dp+2)=(byte)background.blue; } else { byte w, v=gamma_to_1[*sp]; // png_composite(w, v, a, background_1->red); w=(byte)(((ushort)v*(ushort)a+background_1.red*(ushort)(255-(ushort)a)+(ushort)127)/255); *dp=gamma_from_1[w]; v=gamma_to_1[*(sp+1)]; //png_composite(w, v, a, background_1->green); w=(byte)(((ushort)v*(ushort)a+background_1.green*(ushort)(255-(ushort)a)+(ushort)127)/255); *(dp+1)=gamma_from_1[w]; v=gamma_to_1[*(sp+2)]; //png_composite(w, v, a, background_1->blue); w=(byte)(((ushort)v*(ushort)a+background_1.blue*(ushort)(255-(ushort)a)+(ushort)127)/255); *(dp+2)=gamma_from_1[w]; } } } else { for(i=0; i<row_width; i++, sp+=4, dp+=3) { byte a=*(sp+3); if(a==0xff) { *dp=*sp; *(dp+1)=*(sp+1); *(dp+2)=*(sp+2); } else if(a==0) { *dp=(byte)background.red; *(dp+1)=(byte)background.green; *(dp+2)=(byte)background.blue; } else { //png_composite(*dp, *sp, a, background->red); //png_composite(*(dp+1), *(sp+1), a, background->green); //png_composite(*(dp+2), *(sp+2), a, background->blue); *dp=(byte)(((ushort)(*sp)*(ushort)a+background.red*(ushort)(255-(ushort)a)+(ushort)127)/255); *(dp+1)=(byte)(((ushort)(*(sp+1))*(ushort)a+background.green*(ushort)(255-(ushort)a)+(ushort)127)/255); *(dp+2)=(byte)(((ushort)(*(sp+2))*(ushort)a+background.blue*(ushort)(255-(ushort)a)+(ushort)127)/255); } } } } else //if (row_info->bit_depth==16) { if(gamma_16!=null&&gamma_16_from_1!=null&&gamma_16_to_1!=null) { for(i=0; i<row_width; i++, sp+=8, dp+=6) { ushort a=(ushort)(((ushort)(*(sp+6))<<8)+(ushort)(*(sp+7))); if(a==(ushort)0xffff) { ushort v=gamma_16[*(sp+1)>>gamma_shift][*sp]; *dp=(byte)((v>>8)&0xff); *(dp+1)=(byte)(v&0xff); v=gamma_16[*(sp+3)>>gamma_shift][*(sp+2)]; *(dp+2)=(byte)((v>>8)&0xff); *(dp+3)=(byte)(v&0xff); v=gamma_16[*(sp+5)>>gamma_shift][*(sp+4)]; *(dp+4)=(byte)((v>>8)&0xff); *(dp+5)=(byte)(v&0xff); } else if(a==0) { // background is already in screen gamma *dp=(byte)((background.red>>8)&0xff); *(dp+1)=(byte)(background.red&0xff); *(dp+2)=(byte)((background.green>>8)&0xff); *(dp+3)=(byte)(background.green&0xff); *(dp+4)=(byte)((background.blue>>8)&0xff); *(dp+5)=(byte)(background.blue&0xff); } else { ushort w, x, v=gamma_16_to_1[*(sp+1)>>gamma_shift][*sp]; //png_composite_16(w, v, a, background_1->red); w=(ushort)(((uint)v*(uint)a+(uint)background_1.red*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); x=gamma_16_from_1[((w&0xff)>>gamma_shift)][w>>8]; *dp=(byte)((x>>8)&0xff); *(dp+1)=(byte)(x&0xff); v=gamma_16_to_1[*(sp+3)>>gamma_shift][*(sp+2)]; //png_composite_16(w, v, a, background_1->green); w=(ushort)(((uint)v*(uint)a+(uint)background_1.green*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); x=gamma_16_from_1[((w&0xff)>>gamma_shift)][w>>8]; *(dp+2)=(byte)((x>>8)&0xff); *(dp+3)=(byte)(x&0xff); v=gamma_16_to_1[*(sp+5)>>gamma_shift][*(sp+4)]; //png_composite_16(w, v, a, background_1->blue); w=(ushort)(((uint)v*(uint)a+(uint)background_1.blue*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); x=gamma_16_from_1[(w&0xff)>>gamma_shift][w>>8]; *(dp+4)=(byte)((x>>8)&0xff); *(dp+5)=(byte)(x&0xff); } } } else { for(i=0; i<row_width; i++, sp+=8, dp+=6) { ushort a=(ushort)(((ushort)(*(sp+6))<<8)+(ushort)(*(sp+7))); if(a==(ushort)0xffff) { //memcpy(dp, sp, 6); *dp=*sp; *(dp+1)=*(sp+1); *(dp+2)=*(sp+2); *(dp+3)=*(sp+3); *(dp+4)=*(sp+4); *(dp+5)=*(sp+5); } else if(a==0) { *dp=(byte)((background.red>>8)&0xff); *(dp+1)=(byte)(background.red&0xff); *(dp+2)=(byte)((background.green>>8)&0xff); *(dp+3)=(byte)(background.green&0xff); *(dp+4)=(byte)((background.blue>>8)&0xff); *(dp+5)=(byte)(background.blue&0xff); } else { ushort v; ushort r=(ushort)(((*sp)<<8)+*(sp+1)); ushort g=(ushort)(((*(sp+2))<<8)+*(sp+3)); ushort b=(ushort)(((*(sp+4))<<8)+*(sp+5)); //png_composite_16(v, r, a, background->red); v=(ushort)(((uint)r*(uint)a+(uint)background.red*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); *dp=(byte)((v>>8)&0xff); *(dp+1)=(byte)(v&0xff); //png_composite_16(v, g, a, background->green); v=(ushort)(((uint)g*(uint)a+(uint)background.green*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); *(dp+2)=(byte)((v>>8)&0xff); *(dp+3)=(byte)(v&0xff); //png_composite_16(v, b, a, background->blue); v=(ushort)(((uint)b*(uint)a+(uint)background.blue*(uint)(65535-(uint)a)+(uint)32767)/(uint)65535); *(dp+4)=(byte)((v>>8)&0xff); *(dp+5)=(byte)(v&0xff); } } } } } break; } // switch(row_info->color_type) } // fixed if((row_info.color_type&PNG_COLOR_TYPE.ALPHA_MASK)==PNG_COLOR_TYPE.ALPHA_MASK) { row_info.color_type&=~PNG_COLOR_TYPE.ALPHA_MASK; row_info.channels--; row_info.pixel_depth=(byte)(row_info.channels*row_info.bit_depth); row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); } }
unsafe void png_write_find_filter(png_row_info row_info) { PNG_FILTER filter_to_do=do_filter; uint row_bytes=rowbytes; int num_p_filters=(int)num_prev_filters; // find out how many bytes offset each pixel is uint bpp=(uint)(pixel_depth+7)>>3; byte[] prev_row=this.prev_row; byte[] best_row=this.row_buf; byte[] row_buf=best_row; uint mins=PNG_MAXSUM; // The prediction method we use is to find which method provides the // smallest value when summing the absolute values of the distances // from zero, using anything>=128 as negative numbers. This is known // as the "minimum sum of absolute differences" heuristic. Other // heuristics are the "weighted minimum sum of absolute differences" // (experimental and can in theory improve compression), and the "zlib // predictive" method (not implemented yet), which does test compressions // of lines using different filter methods, and then chooses the // (series of) filter(s) that give minimum compressed data size (VERY // computationally expensive). // // GRR 980525: consider also // (1) minimum sum of absolute differences from running average (i.e., // keep running sum of non-absolute differences&count of bytes) // [track dispersion, too? restart average if dispersion too large?] // (1b)minimum sum of absolute differences from sliding average, probably // with window size<=deflate window (usually 32K) // (2) minimum sum of squared differences from zero or running average // (i.e., ~ root-mean-square approach) // We don't need to test the 'no filter' case if this is the only filter // that has been chosen, as it doesn't actually do anything to the data. if((filter_to_do&PNG_FILTER.NONE)==PNG_FILTER.NONE&&filter_to_do!=PNG_FILTER.NONE) { uint sum=0; fixed(byte* row_buf_=row_buf) { byte* rp=row_buf_+1; for(uint i=0; i<row_bytes; i++, rp++) { uint v=*rp; sum+=(v<128)?v:256-v; } } if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint sumlo=sum&PNG_LOMASK; uint sumhi=(sum>>PNG_HISHIFT)&PNG_HIMASK; // Gives us some footroom // Reduce the sum if we match any of the previous rows for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.NONE) { sumlo=(sumlo*filter_weights[j])>>PNG.WEIGHT_SHIFT; sumhi=(sumhi*filter_weights[j])>>PNG.WEIGHT_SHIFT; } } // Factor in the cost of this filter (this is here for completeness, // but it makes no sense to have a "cost" for the NONE filter, as // it has the minimum possible computational cost-none). sumlo=(sumlo*filter_costs[(byte)PNG_FILTER_VALUE.NONE])>>PNG.COST_SHIFT; sumhi=(sumhi*filter_costs[(byte)PNG_FILTER_VALUE.NONE])>>PNG.COST_SHIFT; if(sumhi>PNG_HIMASK) sum=PNG_MAXSUM; else sum=(sumhi<<PNG_HISHIFT)+sumlo; } mins=sum; } // sub filter if(filter_to_do==PNG_FILTER.SUB) // it's the only filter so no testing is needed { fixed(byte* row_buf_=row_buf, sub_row_=sub_row) { byte* rp=row_buf_+1, dp=sub_row_+1, lp=row_buf_+1; uint i=0; for(; i<bpp; i++, rp++, dp++) *dp=*rp; for(; i<row_bytes; i++, rp++, lp++, dp++) *dp=(byte)(((int)*rp-(int)*lp)&0xff); } best_row=sub_row; } else if((filter_to_do&PNG_FILTER.SUB)==PNG_FILTER.SUB) { uint sum=0, lmins=mins; // We temporarily increase the "minimum sum" by the factor we // would reduce the sum of this filter, so that we can do the // early exit comparison without scaling the sum each time. if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint lmlo=lmins&PNG_LOMASK; uint lmhi=(lmins>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.SUB) { lmlo=(lmlo*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; lmhi=(lmhi*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; } } lmlo=(lmlo*inv_filter_costs[(byte)PNG_FILTER_VALUE.SUB])>>PNG.COST_SHIFT; lmhi=(lmhi*inv_filter_costs[(byte)PNG_FILTER_VALUE.SUB])>>PNG.COST_SHIFT; if(lmhi>PNG_HIMASK) lmins=PNG_MAXSUM; else lmins=(lmhi<<PNG_HISHIFT)+lmlo; } fixed(byte* row_buf_=row_buf, sub_row_=sub_row) { byte* rp=row_buf_+1, dp=sub_row_+1, lp=row_buf_+1; uint i=0; for(; i<bpp; i++, rp++, dp++) { uint v=*dp=*rp; sum+=(v<128)?v:256-v; } for(; i<row_bytes; i++, rp++, lp++, dp++) { uint v=*dp=(byte)(((int)*rp-(int)*lp)&0xff); sum+=(v<128)?v:256-v; if(sum>lmins) break; // We are already worse, don't continue. } } if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint sumlo=sum&PNG_LOMASK; uint sumhi=(sum>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.SUB) { sumlo=(sumlo*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; sumhi=(sumhi*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; } } sumlo=(sumlo*inv_filter_costs[(byte)PNG_FILTER_VALUE.SUB])>>PNG.COST_SHIFT; sumhi=(sumhi*inv_filter_costs[(byte)PNG_FILTER_VALUE.SUB])>>PNG.COST_SHIFT; if(sumhi>PNG_HIMASK) sum=PNG_MAXSUM; else sum=(sumhi<<PNG_HISHIFT)+sumlo; } if(sum<mins) { mins=sum; best_row=sub_row; } } // up filter if(filter_to_do==PNG_FILTER.UP) { fixed(byte* row_buf_=row_buf, up_row_=up_row, prev_row_=prev_row) { byte* rp=row_buf_+1, dp=up_row_+1, pp=prev_row_+1; for(uint i=0; i<row_bytes; i++, rp++, pp++, dp++) *dp=(byte)(((int)*rp-(int)*pp)&0xff); } best_row=up_row; } else if((filter_to_do&PNG_FILTER.UP)==PNG_FILTER.UP) { uint sum=0, lmins=mins; if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint lmlo=lmins&PNG_LOMASK; uint lmhi=(lmins>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.UP) { lmlo=(lmlo*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; lmhi=(lmhi*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; } } lmlo=(lmlo*inv_filter_costs[(byte)PNG_FILTER_VALUE.UP])>>PNG.COST_SHIFT; lmhi=(lmhi*inv_filter_costs[(byte)PNG_FILTER_VALUE.UP])>>PNG.COST_SHIFT; if(lmhi>PNG_HIMASK) lmins=PNG_MAXSUM; else lmins=(lmhi<<PNG_HISHIFT)+lmlo; } fixed(byte* row_buf_=row_buf, up_row_=up_row, prev_row_=prev_row) { byte* rp=row_buf_+1, dp=up_row_+1, pp=prev_row_+1; for(uint i=0; i<row_bytes; i++) { uint v=*dp++=(byte)(((int)*rp++-(int)*pp++)&0xff); sum+=(v<128)?v:256-v; if(sum>lmins) break; // We are already worse, don't continue. } } if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint sumlo=sum&PNG_LOMASK; uint sumhi=(sum>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.UP) { sumlo=(sumlo*filter_weights[j])>>PNG.WEIGHT_SHIFT; sumhi=(sumhi*filter_weights[j])>>PNG.WEIGHT_SHIFT; } } sumlo=(sumlo*filter_costs[(byte)PNG_FILTER_VALUE.UP])>>PNG.COST_SHIFT; sumhi=(sumhi*filter_costs[(byte)PNG_FILTER_VALUE.UP])>>PNG.COST_SHIFT; if(sumhi>PNG_HIMASK) sum=PNG_MAXSUM; else sum=(sumhi<<PNG_HISHIFT)+sumlo; } if(sum<mins) { mins=sum; best_row=up_row; } } // avg filter if(filter_to_do==PNG_FILTER.AVG) { fixed(byte* row_buf_=row_buf, avg_row_=avg_row, prev_row_=prev_row) { byte* rp=row_buf_+1, dp=avg_row_+1, pp=prev_row_+1, lp=row_buf_+1; uint i=0; for(; i<bpp; i++) *dp++=(byte)(((int)*rp++-((int)*pp++/2))&0xff); for(; i<row_bytes; i++) *dp++=(byte)(((int)*rp++-(((int)*pp+++(int)*lp++)/2))&0xff); } best_row=avg_row; } else if((filter_to_do&PNG_FILTER.AVG)==PNG_FILTER.AVG) { uint sum=0, lmins=mins; if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint lmlo=lmins&PNG_LOMASK; uint lmhi=(lmins>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.AVG) { lmlo=(lmlo*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; lmhi=(lmhi*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; } } lmlo=(lmlo*inv_filter_costs[(byte)PNG_FILTER_VALUE.AVG])>>PNG.COST_SHIFT; lmhi=(lmhi*inv_filter_costs[(byte)PNG_FILTER_VALUE.AVG])>>PNG.COST_SHIFT; if(lmhi>PNG_HIMASK) lmins=PNG_MAXSUM; else lmins=(lmhi<<PNG_HISHIFT)+lmlo; } fixed(byte* row_buf_=row_buf, avg_row_=avg_row, prev_row_=prev_row) { byte* rp=row_buf_+1, dp=avg_row_+1, pp=prev_row_+1, lp=row_buf_+1; uint i=0; for(; i<bpp; i++) { uint v=*dp++=(byte)(((int)*rp++-((int)*pp++/2))&0xff); sum+=(v<128)?v:256-v; } for(; i<row_bytes; i++) { uint v=*dp++=(byte)(((int)*rp++-(((int)*pp+++(int)*lp++)/2))&0xff); sum+=(v<128)?v:256-v; if(sum>lmins) break; // We are already worse, don't continue. } } if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint sumlo=sum&PNG_LOMASK; uint sumhi=(sum>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.NONE) { sumlo=(sumlo*filter_weights[j])>>PNG.WEIGHT_SHIFT; sumhi=(sumhi*filter_weights[j])>>PNG.WEIGHT_SHIFT; } } sumlo=(sumlo*filter_costs[(byte)PNG_FILTER_VALUE.AVG])>>PNG.COST_SHIFT; sumhi=(sumhi*filter_costs[(byte)PNG_FILTER_VALUE.AVG])>>PNG.COST_SHIFT; if(sumhi>PNG_HIMASK) sum=PNG_MAXSUM; else sum=(sumhi<<PNG_HISHIFT)+sumlo; } if(sum<mins) { mins=sum; best_row=avg_row; } } // Paeth filter if(filter_to_do==PNG_FILTER.PAETH) { fixed(byte* row_buf_=row_buf, paeth_row_=paeth_row, prev_row_=prev_row) { byte* rp=row_buf_+1, dp=paeth_row_+1, pp=prev_row_+1, cp=prev_row_+1, lp=row_buf_+1; uint i=0; for(; i<bpp; i++) *dp++=(byte)(((int)*rp++-(int)*pp++)&0xff); for(; i<row_bytes; i++) { int b=*pp++; int c=*cp++; int a=*lp++; int p=b-c; int pc=a-c; //int pa=abs(p); //int pb=abs(pc); //pc=abs(p+pc); int pa=p<0?-p:p; int pb=pc<0?-pc:pc; pc=(p+pc)<0?-(p+pc):p+pc; p=(pa<=pb&&pa<=pc)?a:(pb<=pc)?b:c; *dp++=(byte)(((int)*rp++-p)&0xff); } } best_row=paeth_row; } else if((filter_to_do&PNG_FILTER.PAETH)==PNG_FILTER.PAETH) { uint sum=0, lmins=mins; if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint lmlo=lmins&PNG_LOMASK; uint lmhi=(lmins>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.PAETH) { lmlo=(lmlo*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; lmhi=(lmhi*inv_filter_weights[j])>>PNG.WEIGHT_SHIFT; } } lmlo=(lmlo*inv_filter_costs[(byte)PNG_FILTER_VALUE.PAETH])>>PNG.COST_SHIFT; lmhi=(lmhi*inv_filter_costs[(byte)PNG_FILTER_VALUE.PAETH])>>PNG.COST_SHIFT; if(lmhi>PNG_HIMASK) lmins=PNG_MAXSUM; else lmins=(lmhi<<PNG_HISHIFT)+lmlo; } fixed(byte* row_buf_=row_buf, paeth_row_=paeth_row, prev_row_=prev_row) { byte* rp=row_buf_+1, dp=paeth_row_+1, pp=prev_row_+1, cp=prev_row_+1, lp=row_buf_+1; uint i=0; for(; i<bpp; i++) { uint v=*dp++=(byte)(((int)*rp++-(int)*pp++)&0xff); sum+=(v<128)?v:256-v; } for(; i<row_bytes; i++) { int b=*pp++; int c=*cp++; int a=*lp++; int p=b-c; int pc=a-c; //int pa=abs(p); //int pb=abs(pc); //pc=abs(p+pc); int pa=p<0?-p:p; int pb=pc<0?-pc:pc; pc=(p+pc)<0?-(p+pc):p+pc; p=(pa<=pb&&pa<=pc)?a:(pb<=pc)?b:c; uint v=*dp++=(byte)(((int)*rp++-p)&0xff); sum+=(v<128)?v:256-v; if(sum>lmins) break; // We are already worse, don't continue. } } if(heuristic_method==PNG_FILTER_HEURISTIC.WEIGHTED) { uint sumlo=sum&PNG_LOMASK; uint sumhi=(sum>>PNG_HISHIFT)&PNG_HIMASK; for(int j=0; j<num_p_filters; j++) { if(prev_filters[j]==(byte)PNG_FILTER_VALUE.PAETH) { sumlo=(sumlo*filter_weights[j])>>PNG.WEIGHT_SHIFT; sumhi=(sumhi*filter_weights[j])>>PNG.WEIGHT_SHIFT; } } sumlo=(sumlo*filter_costs[(byte)PNG_FILTER_VALUE.PAETH])>>PNG.COST_SHIFT; sumhi=(sumhi*filter_costs[(byte)PNG_FILTER_VALUE.PAETH])>>PNG.COST_SHIFT; if(sumhi>PNG_HIMASK) sum=PNG_MAXSUM; else sum=(sumhi<<PNG_HISHIFT)+sumlo; } if(sum<mins) best_row=paeth_row; } // Do the actual writing of the filtered row data from the chosen filter. png_write_filtered_row(best_row); // Save the type of filter we picked this time for future calculations if(num_prev_filters>0) { int j; for(j=1; j<num_p_filters; j++) prev_filters[j]=prev_filters[j-1]; prev_filters[j]=best_row[0]; } }
// Gamma correct the image, avoiding the alpha channel. Make sure // you do this after you deal with the transparency issue on grayscale // or RGB images. If your bit depth is 8, use gamma_table, if it // is 16, use gamma_16_table and gamma_shift. Build these with // build_gamma_table(). static unsafe void png_do_gamma(ref png_row_info row_info, byte[] row, byte[] gamma_table, ushort[][] gamma_16_table, int gamma_shift) { if(((row_info.bit_depth>8||gamma_table==null)&&(row_info.bit_depth!=16||gamma_16_table==null))) return; uint row_width=row_info.width; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value switch(row_info.color_type) { case PNG_COLOR_TYPE.RGB: { if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { *sp=gamma_table[*sp]; sp++; *sp=gamma_table[*sp]; sp++; *sp=gamma_table[*sp]; sp++; } } else // if(row_info->bit_depth==16) { for(uint i=0; i<row_width; i++) { ushort v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=2; v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=2; v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=2; } } } break; case PNG_COLOR_TYPE.RGB_ALPHA: { if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { *sp=gamma_table[*sp]; sp++; *sp=gamma_table[*sp]; sp++; *sp=gamma_table[*sp]; sp++; sp++; } } else // if (row_info->bit_depth==16) { for(uint i=0; i<row_width; i++) { ushort v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=2; v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=2; v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=4; } } } break; case PNG_COLOR_TYPE.GRAY_ALPHA: { if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++, sp+=2) *sp=gamma_table[*sp]; } else // if (row_info->bit_depth==16) { for(uint i=0; i<row_width; i++) { ushort v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=4; } } } break; case PNG_COLOR_TYPE.GRAY: { if(row_info.bit_depth==2) { for(uint i=0; i<row_width; i+=4) { int a=*sp&0xc0; int b=*sp&0x30; int c=*sp&0x0c; int d=*sp&0x03; *sp=(byte)(((((int)gamma_table[a|(a>>2)|(a>>4)|(a>>6)]))&0xc0)|((((int)gamma_table[(b<<2)|b|(b>>2)|(b>>4)])>>2)&0x30)| ((((int)gamma_table[(c<<4)|(c<<2)|c|(c>>2)])>>4)&0x0c)|((((int)gamma_table[(d<<6)|(d<<4)|(d<<2)|d])>>6))); sp++; } } else if(row_info.bit_depth==4) { for(uint i=0; i<row_width; i+=2) { int msb=*sp&0xf0; int lsb=*sp&0x0f; *sp=(byte)((((int)gamma_table[msb|(msb>>4)])&0xf0)|(((int)gamma_table[(lsb<<4)|lsb])>>4)); sp++; } } else if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++, sp++) *sp=gamma_table[*sp]; } else if(row_info.bit_depth==16) { for(uint i=0; i<row_width; i++) { ushort v=gamma_16_table[*(sp+1)>>gamma_shift][*sp]; *sp=(byte)((v>>8)&0xff); *(sp+1)=(byte)(v&0xff); sp+=2; } } } break; } } }
unsafe void png_do_write_swap_alpha(png_row_info row_info, byte[] row) { fixed(byte* row_=row) { byte* sp=row_+1, dp=row_+1; // skip filter value uint row_width=row_info.width; if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) { // This converts from ARGB to RGBA if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { byte save=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=save; } } else // This converts from AARRGGBB to RRGGBBAA { for(uint i=0; i<row_width; i++) { byte save0, save1; save0=*(sp++); save1=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=save0; *(dp++)=save1; } } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA) { // This converts from AG to GA if(row_info.bit_depth==8) { for(uint i=0; i<row_width; i++) { byte save=*(sp++); *(dp++)=*(sp++); *(dp++)=save; } } else // This converts from AAGG to GGAA { for(uint i=0; i<row_width; i++) { byte save0, save1; save0=*(sp++); save1=*(sp++); *(dp++)=*(sp++); *(dp++)=*(sp++); *(dp++)=save0; *(dp++)=save1; } } } } }
// Expands a palette row to an RGB or RGBA row depending // upon whether you supply trans_alpha and num_trans. static unsafe void png_do_expand_palette(ref png_row_info row_info, byte[] row, png_color[] palette, byte[] trans_alpha) { int shift, value; uint row_width=row_info.width; if(row_info.color_type!=PNG_COLOR_TYPE.PALETTE) return; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value if(row_info.bit_depth<8) { switch(row_info.bit_depth) { case 1: { sp+=(row_width-1)>>3; dp+=row_width-1; shift=7-(int)((row_width+7)&0x07); for(uint i=0; i<row_width; i++) { if(((*sp>>shift)&0x01)==0x01) *dp=1; else *dp=0; if(shift==7) { shift=0; sp--; } else shift++; dp--; } } break; case 2: { sp+=(row_width-1)>>2; dp+=row_width-1; shift=(int)((3-((row_width+3)&0x03))<<1); for(uint i=0; i<row_width; i++) { value=(*sp>>shift)&0x03; *dp=(byte)value; if(shift==6) { shift=0; sp--; } else shift+=2; dp--; } } break; case 4: { sp+=(row_width-1)>>1; dp+=row_width-1; shift=(int)((row_width&0x01)<<2); for(uint i=0; i<row_width; i++) { value=(*sp>>shift)&0x0f; *dp=(byte)value; if(shift==4) { shift=0; sp--; } else shift+=4; dp--; } } break; } row_info.bit_depth=8; row_info.pixel_depth=8; row_info.rowbytes=row_width; } // if(row_info.bit_depth<8) if(row_info.bit_depth==8) { sp=row_+1; dp=row_+1; if(trans_alpha!=null&&trans_alpha.Length!=0) { sp+=row_width-1; dp+=(row_width<<2)-1; for(uint i=0; i<row_width; i++) { if(*sp>=trans_alpha.Length) *dp--=0xff; else *dp--=trans_alpha[*sp]; *dp--=palette[*sp].blue; *dp--=palette[*sp].green; *dp--=palette[*sp].red; sp--; } row_info.bit_depth=8; row_info.pixel_depth=32; row_info.rowbytes=row_width*4; row_info.color_type=PNG_COLOR_TYPE.RGB_ALPHA; row_info.channels=4; } else { sp+=row_width-1; dp+=(row_width*3)-1; for(uint i=0; i<row_width; i++) { *dp--=palette[*sp].blue; *dp--=palette[*sp].green; *dp--=palette[*sp].red; sp--; } row_info.bit_depth=8; row_info.pixel_depth=24; row_info.rowbytes=row_width*3; row_info.color_type=PNG_COLOR_TYPE.RGB; row_info.channels=3; } } } }
// undoes intrapixel differencing unsafe void png_do_write_intrapixel(png_row_info row_info, byte[] row) { if((row_info.color_type&PNG_COLOR_TYPE.COLOR_MASK)!=PNG_COLOR_TYPE.COLOR_MASK) return; fixed(byte* row_=row) { byte* rp=row_+1; // skip filter value int bytes_per_pixel; uint row_width=row_info.width; if(row_info.bit_depth==8) { if(row_info.color_type==PNG_COLOR_TYPE.RGB) bytes_per_pixel=3; else if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) bytes_per_pixel=4; else return; for(uint i=0; i<row_width; i++, rp+=bytes_per_pixel) { *(rp)=(byte)((*rp-*(rp+1))&0xff); *(rp+2)=(byte)((*(rp+2)-*(rp+1))&0xff); } } else if(row_info.bit_depth==16) { if(row_info.color_type==PNG_COLOR_TYPE.RGB) bytes_per_pixel=6; else if(row_info.color_type==PNG_COLOR_TYPE.RGB_ALPHA) bytes_per_pixel=8; else return; for(uint i=0; i<row_width; i++, rp+=bytes_per_pixel) { uint s0=(uint)(*(rp)<<8)|*(rp+1); uint s1=(uint)(*(rp+2)<<8)|*(rp+3); uint s2=(uint)(*(rp+4)<<8)|*(rp+5); uint red=(uint)((s0-s1)&0xffffL); uint blue=(uint)((s2-s1)&0xffffL); *(rp)=(byte)((red>>8)&0xff); *(rp+1)=(byte)(red&0xff); *(rp+4)=(byte)((blue>>8)&0xff); *(rp+5)=(byte)(blue&0xff); } } } }
// If the bit depth < 8, it is expanded to 8. Also, if the already // expanded transparency value is supplied, an alpha channel is built. static unsafe void png_do_expand_with_trans_values(ref png_row_info row_info, byte[] row, ref png_color_16 trans_value) { int shift, value; uint row_width=row_info.width; fixed(byte* row_=row) { byte* sp=row_+1; // skip filter value byte* dp=row_+1; // skip filter value if(row_info.color_type==PNG_COLOR_TYPE.GRAY) { ushort gray=trans_value.gray; if(row_info.bit_depth<8) { switch(row_info.bit_depth) { case 1: { gray=(ushort)((gray&0x01)*0xff); sp+=(row_width-1)>>3; dp+=row_width-1; shift=7-(int)((row_width+7)&0x07); for(uint i=0; i<row_width; i++) { if(((*sp>>shift)&0x01)==0x01) *dp=0xff; else *dp=0; if(shift==7) { shift=0; sp--; } else shift++; dp--; } } break; case 2: { gray=(ushort)((gray&0x03)*0x55); sp+=(row_width-1)>>2; dp+=row_width-1; shift=(int)((3-((row_width+3)&0x03))<<1); for(uint i=0; i<row_width; i++) { value=(*sp>>shift)&0x03; *dp=(byte)(value|(value<<2)|(value<<4)|(value<<6)); if(shift==6) { shift=0; sp--; } else shift+=2; dp--; } } break; case 4: { gray=(ushort)((gray&0x0f)*0x11); sp+=(row_width-1)>>1; dp+=row_width-1; shift=(int)((1-((row_width+1)&0x01))<<2); for(uint i=0; i<row_width; i++) { value=(*sp>>shift)&0x0f; *dp=(byte)(value|(value<<4)); if(shift==4) { shift=0; sp--; } else shift=4; dp--; } } break; } row_info.bit_depth=8; row_info.pixel_depth=8; row_info.rowbytes=row_width; // reset to start values sp=row_+1; // skip filter value dp=row_+1; // skip filter value } // if(row_info.bit_depth<8) if(row_info.bit_depth==8) { gray=(byte)(gray&0xff); sp+=row_width-1; dp+=(row_width<<1)-1; for(uint i=0; i<row_width; i++) { if(*sp==gray) *dp--=0; else *dp--=0xff; *dp--=*sp--; } } else if(row_info.bit_depth==16) { byte gray_high=(byte)((gray>>8)&0xff); byte gray_low=(byte)(gray&0xff); sp+=row_info.rowbytes-1; dp+=(row_info.rowbytes<<1)-1; for(uint i=0; i<row_width; i++) { if(*(sp-1)==gray_high&&*(sp)==gray_low) { *dp--=0; *dp--=0; } else { *dp--=0xff; *dp--=0xff; } *dp--=*sp--; *dp--=*sp--; } } row_info.color_type=PNG_COLOR_TYPE.GRAY_ALPHA; row_info.channels=2; row_info.pixel_depth=(byte)(row_info.bit_depth<<1); row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); } else if(row_info.color_type==PNG_COLOR_TYPE.RGB) { if(row_info.bit_depth==8) { byte red=(byte)(trans_value.red&0xff); byte green=(byte)(trans_value.green&0xff); byte blue=(byte)(trans_value.blue&0xff); sp+=row_info.rowbytes-1; dp+=(row_width<<2)-1; for(uint i=0; i<row_width; i++) { if(*(sp-2)==red&&*(sp-1)==green&&*(sp)==blue) *dp--=0; else *dp--=0xff; *dp--=*sp--; *dp--=*sp--; *dp--=*sp--; } } else if(row_info.bit_depth==16) { byte red_high=(byte)((trans_value.red>>8)&0xff); byte green_high=(byte)((trans_value.green>>8)&0xff); byte blue_high=(byte)((trans_value.blue>>8)&0xff); byte red_low=(byte)(trans_value.red&0xff); byte green_low=(byte)(trans_value.green&0xff); byte blue_low=(byte)(trans_value.blue&0xff); sp+=row_info.rowbytes-1; dp+=(row_width<<3)-1; for(uint i=0; i<row_width; i++) { if(*(sp-5)==red_high&&*(sp-4)==red_low&&*(sp-3)==green_high&&*(sp-2)==green_low&&*(sp-1)==blue_high&&*(sp)==blue_low) { *dp--=0; *dp--=0; } else { *dp--=0xff; *dp--=0xff; } *dp--=*sp--; *dp--=*sp--; *dp--=*sp--; *dp--=*sp--; *dp--=*sp--; *dp--=*sp--; } } row_info.color_type=PNG_COLOR_TYPE.RGB_ALPHA; row_info.channels=4; row_info.pixel_depth=(byte)(row_info.bit_depth<<2); row_info.rowbytes=PNG_ROWBYTES(row_info.pixel_depth, row_width); } } }
unsafe void png_read_filter_row(png_row_info row_info, byte[] row, byte[] prev_row, PNG_FILTER_VALUE filter) { fixed(byte* row__=row, prev_row__=prev_row) { byte* row_=row__+1, prev_row_=prev_row__+1; switch(filter) { case PNG_FILTER_VALUE.NONE: break; case PNG_FILTER_VALUE.SUB: { uint istop=row_info.rowbytes; uint bpp=(uint)(row_info.pixel_depth+7)>>3; byte* rp=row_+bpp, lp=row_; for(uint i=bpp; i<istop; i++) { *rp=(byte)(((int)(*rp)+(int)(*lp++))&0xff); rp++; } } break; case PNG_FILTER_VALUE.UP: { uint istop=row_info.rowbytes; byte* rp=row_, pp=prev_row_; for(uint i=0; i<istop; i++) { *rp=(byte)(((int)(*rp)+(int)(*pp++))&0xff); rp++; } } break; case PNG_FILTER_VALUE.AVG: { byte* rp=row_, pp=prev_row_, lp=row_; uint bpp=(uint)(row_info.pixel_depth+7)>>3; uint istop=row_info.rowbytes-bpp; for(uint i=0; i<bpp; i++) { *rp=(byte)(((int)(*rp)+((int)(*pp++)/2))&0xff); rp++; } for(uint i=0; i<istop; i++) { *rp=(byte)(((int)(*rp)+(int)(*pp+++*lp++)/2)&0xff); rp++; } } break; case PNG_FILTER_VALUE.PAETH: { byte* rp=row_, pp=prev_row_, lp=row_, cp=prev_row_; uint bpp=(uint)(row_info.pixel_depth+7)>>3; uint istop=row_info.rowbytes-bpp; for(uint i=0; i<bpp; i++) { *rp=(byte)(((int)(*rp)+(int)(*pp++))&0xff); rp++; } for(uint i=0; i<istop; i++) // use leftover rp, pp { int a=*lp++; int b=*pp++; int c=*cp++; int p=b-c; int pc=a-c; //int pa=abs(p); //int pb=abs(pc); //pc=abs(p+pc); int pa=p<0?-p:p; int pb=pc<0?-pc:pc; pc=(p+pc)<0?-(p+pc):p+pc; p=(pa<=pb&&pa<=pc)?a:(pb<=pc)?b:c; *rp=(byte)(((int)(*rp)+p)&0xff); rp++; } } break; default: Debug.WriteLine("Ignoring bad adaptive filter type"); *row_=0; break; } } }
// invert monochrome grayscale data static unsafe void png_do_invert(ref png_row_info row_info, byte[] row) { uint istop=row_info.rowbytes; fixed(byte* row_=row) { byte* rp=row_+1;// skip filter value // This test removed from libpng version 1.0.13 and 1.2.0: // if(row_info->bit_depth==1 && if(row_info.color_type==PNG_COLOR_TYPE.GRAY) { for(uint i=0; i<istop; i++) { *rp=(byte)(~(*rp)); rp++; } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA&&row_info.bit_depth==8) { for(uint i=0; i<istop; i+=2) { *rp=(byte)(~(*rp)); rp+=2; } } else if(row_info.color_type==PNG_COLOR_TYPE.GRAY_ALPHA&&row_info.bit_depth==16) { for(uint i=0; i<istop; i+=4) { *rp=(byte)(~(*rp)); *(rp+1)=(byte)(~(*(rp+1))); rp+=4; } } } }