Beispiel #1
0
        // 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];
                    }
                }
            }
        }
Beispiel #2
0
        // 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;
                            }
                        }
                    }
                }
            }
        }
Beispiel #3
0
        // 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;
                    }
                }
            }
        }
Beispiel #4
0
        // 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];
				}
			}
		}
Beispiel #19
0
        // 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;
            }
        }
Beispiel #20
0
        // 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;
					}
				}
			}
		}