public PNG_INFO png_get_PLTE(ref png_color[] palette) { if((info_ptr_valid&PNG_INFO.PLTE)!=PNG_INFO.PLTE) return PNG_INFO.None; palette=info_ptr_palette; return PNG_INFO.PLTE; }
public void png_set_PLTE(png_color[] palette) { if(palette==null||palette.Length==0) { this.palette=info_ptr_palette=null; info_ptr_valid&=~PNG_INFO.PLTE; return; } if(palette.Length>PNG.MAX_PALETTE_LENGTH) { if(info_ptr_color_type==PNG_COLOR_TYPE.PALETTE) throw new PNG_Exception("Invalid palette length"); else { Debug.WriteLine("Invalid palette length"); return; } } this.palette=info_ptr_palette=palette; info_ptr_valid|=PNG_INFO.PLTE; }
// read and check the palette void png_handle_PLTE(uint length) { if((mode&PNG_MODE.HAVE_IHDR)!=PNG_MODE.HAVE_IHDR) throw new PNG_Exception("Missing IHDR before PLTE"); else if((mode&PNG_MODE.HAVE_IDAT)==PNG_MODE.HAVE_IDAT) { Debug.WriteLine("Invalid PLTE after IDAT"); png_crc_finish(length); return; } else if((mode&PNG_MODE.HAVE_PLTE)==PNG_MODE.HAVE_PLTE) throw new PNG_Exception("Duplicate PLTE chunk"); mode|=PNG_MODE.HAVE_PLTE; if((color_type&PNG_COLOR_TYPE.COLOR_MASK)!=PNG_COLOR_TYPE.COLOR_MASK) { Debug.WriteLine("Ignoring PLTE chunk in grayscale PNG"); png_crc_finish(length); return; } png_color[] palette=new png_color[PNG.MAX_PALETTE_LENGTH]; if((length>3*PNG.MAX_PALETTE_LENGTH)||length%3!=0) { if(color_type!=PNG_COLOR_TYPE.PALETTE) { Debug.WriteLine("Invalid palette chunk"); png_crc_finish(length); return; } else throw new PNG_Exception("Invalid palette chunk"); } ushort num=(ushort)(length/3); for(ushort i=0; i<num; i++) { png_crc_read(buf4, 3); palette[i].red=buf4[0]; palette[i].green=buf4[1]; palette[i].blue=buf4[2]; } // If we actually NEED the PLTE chunk (ie for a paletted image), we do // whatever the normal CRC configuration tells us. However, if we // have an RGB image, the PLTE can be considered ancillary, so // we will act as though it is. png_crc_finish(0); png_set_PLTE(palette); if(color_type==PNG_COLOR_TYPE.PALETTE) { if((info_ptr_valid&PNG_INFO.tRNS)==PNG_INFO.tRNS) { if(num_trans>num) { Debug.WriteLine("Truncating incorrect tRNS chunk length"); byte[] newtrans=new byte[num]; Array.Copy(trans_alpha, newtrans, num); trans_alpha=newtrans; num_trans=num; } if(info_ptr_num_trans>num) { Debug.WriteLine("Truncating incorrect info tRNS chunk length"); byte[] newtrans=new byte[num]; Array.Copy(trans_alpha, newtrans, num); trans_alpha=newtrans; info_ptr_num_trans=num; } } } }
// write the palette. We are careful not to trust png_color to be in the // correct order for PNG, so people can redefine it to any convenient // structure. void png_write_PLTE(png_color[] palette) { uint num_pal=0; if(palette!=null) num_pal=(uint)palette.Length; if(((mng_features_permitted&PNG_FLAG_MNG.EMPTY_PLTE)!=PNG_FLAG_MNG.EMPTY_PLTE&&num_pal==0)||num_pal>256) { if(color_type==PNG_COLOR_TYPE.PALETTE) throw new PNG_Exception("Invalid number of colors in palette"); else { Debug.WriteLine("Invalid number of colors in palette"); return; } } if((color_type&PNG_COLOR_TYPE.COLOR_MASK)!=PNG_COLOR_TYPE.COLOR_MASK) { Debug.WriteLine("Ignoring request to write a PLTE chunk in grayscale PNG"); return; } png_write_chunk_start(PNG.PLTE, num_pal*3); for(uint i=0; i<num_pal; i++) { buf4[0]=palette[i].red; buf4[1]=palette[i].green; buf4[2]=palette[i].blue; png_write_chunk_data(buf4, 3); } png_write_chunk_end(); mode|=PNG_MODE.HAVE_PLTE; }
// Save typing and make code easier to understand static int PNG_COLOR_DIST(png_color c1, png_color c2) { return(Math.Abs((int)c1.red - c2.red) + Math.Abs((int)c1.green - c2.green) + Math.Abs((int)c1.blue - c2.blue)); }
// 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; } } } }
// Build a grayscale palette. Palette is assumed to be 1 << bit_depth // large of png_color. This lets grayscale images be treated as // paletted. Most useful for gamma correction and simplification // of code. public static void png_build_grayscale_palette(int bit_depth, png_color[] palette) { if(palette==null||palette.Length==0) return; int num_palette; int color_inc; switch(bit_depth) { case 1: num_palette=2; color_inc=0xff; break; case 2: num_palette=4; color_inc=0x55; break; case 4: num_palette=16; color_inc=0x11; break; case 8: num_palette=256; color_inc=1; break; default: num_palette=0; color_inc=0; break; } for(int i=0, v=0; i<num_palette; i++, v+=color_inc) { palette[i].red=(byte)v; palette[i].green=(byte)v; palette[i].blue=(byte)v; } }
// Prior to libpng-1.4.2, this was png_set_dither(). public void png_set_quantize(png_color[] palette, byte maximum_colors, ushort[] histogram, bool full_quantize) { transformations|=PNG_TRANSFORMATION.QUANTIZE; if(!full_quantize) { quantize_index=new byte[palette.Length]; for(int i=0; i<palette.Length; i++) quantize_index[i]=(byte)i; } if(palette.Length>maximum_colors) { if(histogram!=null) { // This is easy enough, just throw out the least used colors. // Perhaps not the best solution, but good enough. // initialize an array to sort colors byte[] quantize_sort=new byte[palette.Length]; // initialize the quantize_sort array for(int i=0; i<palette.Length; i++) quantize_sort[i]=(byte)i; // Find the least used palette entries by starting a // bubble sort, and running it until we have sorted // out enough colors. Note that we don't care about // sorting all the colors, just finding which are // least used. for(int i=palette.Length-1; i>=maximum_colors; i--) { bool done=true; // to stop early if the list is pre-sorted for(int j=0; j<i; j++) { if(histogram[quantize_sort[j]]<histogram[quantize_sort[j+1]]) { byte t=quantize_sort[j]; quantize_sort[j]=quantize_sort[j+1]; quantize_sort[j+1]=t; done=false; } } if(done) break; } // swap the palette around, and set up a table, if necessary if(full_quantize) { int j=palette.Length; // put all the useful colors within the max, but don't move the others for(int i=0; i<maximum_colors; i++) { if(quantize_sort[i]>=maximum_colors) { do { j--; } while(quantize_sort[j]>=maximum_colors); palette[i]=palette[j]; } } } else { int j=palette.Length; // move all the used colors inside the max limit, and develop a translation table for(int i=0; i<maximum_colors; i++) { // only move the colors we need to if(quantize_sort[i]>=maximum_colors) { do { j--; } while(quantize_sort[j]>=maximum_colors); png_color tmp_color=palette[j]; palette[j]=palette[i]; palette[i]=tmp_color; // indicate where the color went quantize_index[j]=(byte)i; quantize_index[i]=(byte)j; } } // find closest color for those colors we are not using for(int i=0; i<palette.Length; i++) { if(quantize_index[i]>=maximum_colors) { // find the closest color to one we threw out int d_index=quantize_index[i]; int min_d=PNG_COLOR_DIST(palette[d_index], palette[0]); int min_k=0; for(int k=1; k<maximum_colors; k++) { int d=PNG_COLOR_DIST(palette[d_index], palette[k]); if(d<min_d) { min_d=d; min_k=k; } } // point to closest color quantize_index[i]=(byte)min_k; } } } quantize_sort=null; } else // if(histogram!=null) { // This is much harder to do simply (and quickly). Perhaps // we need to go through a median cut routine, but those // don't always behave themselves with only a few colors // as input. So we will just find the closest two colors, // and throw out one of them (chosen somewhat randomly). // [We don't understand this at all, so if someone wants to // work on improving it, be our guest - AED, GRP] // initialize palette index arrays byte[] index_to_palette=new byte[palette.Length]; byte[] palette_to_index=new byte[palette.Length]; // initialize the sort array for(int i=0; i<palette.Length; i++) { index_to_palette[i]=(byte)i; palette_to_index[i]=(byte)i; } png_dsort[] hash=new png_dsort[769]; int num_new_palette=palette.Length; // initial wild guess at how far apart the farthest pixel // pair we will be eliminating will be. Larger // numbers mean more areas will be allocated, Smaller // numbers run the risk of not saving enough data, and // having to do this all over again. // // I have not done extensive checking on this number. int max_d=96; png_dsort t=null; while(num_new_palette>maximum_colors) { for(int i=0; i<num_new_palette-1; i++) { for(int j=i+1; j<num_new_palette; j++) { int d=PNG_COLOR_DIST(palette[i], palette[j]); if(d<=max_d) { try { t=new png_dsort(); } catch(Exception) { t=null; break; } t.next=hash[d]; t.left=(byte)i; t.right=(byte)j; hash[d]=t; } } if(t==null) break; } if(t!=null) { for(int i=0; i<=max_d; i++) { if(hash[i]!=null) { for(png_dsort p=hash[i]; p!=null; p=p.next) { if(index_to_palette[p.left]<num_new_palette&&index_to_palette[p.right]<num_new_palette) { int j, next_j; if((num_new_palette&0x01)==0x01) { j=p.left; next_j=p.right; } else { j=p.right; next_j=p.left; } num_new_palette--; palette[index_to_palette[j]]=palette[num_new_palette]; if(!full_quantize) { for(int k=0; k<palette.Length; k++) { if(quantize_index[k]==index_to_palette[j]) quantize_index[k]=index_to_palette[next_j]; if(quantize_index[k]==num_new_palette) quantize_index[k]=index_to_palette[j]; } } index_to_palette[palette_to_index[num_new_palette]]=index_to_palette[j]; palette_to_index[index_to_palette[j]]=palette_to_index[num_new_palette]; index_to_palette[j]=(byte)num_new_palette; palette_to_index[num_new_palette]=(byte)j; } if(num_new_palette<=maximum_colors) break; } if(num_new_palette<=maximum_colors) break; } } // for } for(int i=0; i<769; i++) { if(hash[i]!=null) { png_dsort p=hash[i]; while(p!=null) { t=p.next; p.next=null; p=t; } } hash[i]=null; } max_d+=96; } t=null; hash=null; palette_to_index=null; index_to_palette=null; } png_color[] tmppal=new png_color[maximum_colors]; Array.Copy(palette, tmppal, maximum_colors); palette=tmppal; } // if(num_palette>maximum_colors) if(this.palette==null) this.palette=palette; if(!full_quantize) return; int total_bits=PNG_QUANTIZE_RED_BITS+PNG_QUANTIZE_GREEN_BITS+PNG_QUANTIZE_BLUE_BITS; int num_red=1<<PNG_QUANTIZE_RED_BITS; int num_green=1<<PNG_QUANTIZE_GREEN_BITS; int num_blue=1<<PNG_QUANTIZE_BLUE_BITS; uint num_entries=(uint)(1<<total_bits); palette_lookup=new byte[num_entries]; byte[] distance=new byte[num_entries]; for(int i=0; i<num_entries; i++) distance[i]=0xff; //memset(distance, 0xff, num_entries*sizeof(byte)); for(int i=0; i<palette.Length; i++) { int r=palette[i].red>>(8-PNG_QUANTIZE_RED_BITS); int g=palette[i].green>>(8-PNG_QUANTIZE_GREEN_BITS); int b=palette[i].blue>>(8-PNG_QUANTIZE_BLUE_BITS); for(int ir=0; ir<num_red; ir++) { // int dr=abs(ir-r); int dr=(ir>r)?ir-r:r-ir; int index_r=ir<<(PNG_QUANTIZE_BLUE_BITS+PNG_QUANTIZE_GREEN_BITS); for(int ig=0; ig<num_green; ig++) { // int dg=abs(ig-g); int dg=(ig>g)?ig-g:g-ig; int dt=dr+dg; int dm=(dr>dg)?dr:dg; int index_g=index_r|ig<<PNG_QUANTIZE_BLUE_BITS; for(int ib=0; ib<num_blue; ib++) { int d_index=index_g|ib; // int db=abs(ib-b); int db=(ib>b)?ib-b:b-ib; int dmax=(dm>db)?dm:db; int d=dmax+dt+db; if(d<distance[d_index]) { distance[d_index]=(byte)d; palette_lookup[d_index]=(byte)i; } } } } } distance=null; }
public void png_set_dither(png_color[] palette, byte maximum_colors, ushort[] histogram, bool full_dither) { png_set_quantize(palette, maximum_colors, histogram, full_dither); }
// Save typing and make code easier to understand static int PNG_COLOR_DIST(png_color c1, png_color c2) { return Math.Abs((int)c1.red-c2.red)+Math.Abs((int)c1.green-c2.green)+Math.Abs((int)c1.blue-c2.blue); }