// Load a PNG image from the specified stream. The first 4 bytes // have already been read and discarded. public static void Load(Stream stream, Image image) { byte[] buffer = new byte [1024]; int width = 0; int height = 0; int bitDepth = 0; int colorType = 0; int compressionMethod = 0; int filterMethod = 0; int interlaceMethod = 0; Frame frame = null; PixelFormat format = 0; int index; int significant = 0; ZlibDecompressor decompress = null; ScanlineReader scanlineReader; int pass, passWidth, passHeight; PassFunc passFunc; // Read the rest of the magic number and check it. if(stream.Read(buffer, 0, 4) != 4) { throw new FormatException("could not read magic number"); } if(buffer[0] != (byte)13 || buffer[1] != (byte)10 || buffer[2] != (byte)26 || buffer[3] != (byte)10) { throw new FormatException("invalid magic number"); } // Create a chunk reader for the stream. ChunkReader reader = new ChunkReader(stream, buffer); // Read all of the chunks from the stream. while(reader.Type != IEND) { // Process the critical chunk types. if(reader.Type == IHDR) { // We can only have one header per PNG image. if(image.NumFrames > 0) { throw new FormatException("multiple headers"); } // Read the contents of the image header. if(reader.Read(buffer, 0, 13) != 13) { throw new FormatException("truncated header"); } width = Utils.ReadInt32B(buffer, 0); height = Utils.ReadInt32B(buffer, 4); bitDepth = buffer[8]; colorType = buffer[9]; compressionMethod = buffer[10]; filterMethod = buffer[11]; interlaceMethod = buffer[12]; // Sanity-check the values. if(width < 1 || height < 1) { throw new FormatException("invalid size"); } if(colorType == 0) { if(bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16) { throw new FormatException ("invalid depth for color type 0"); } } else if(colorType == 2 || colorType == 4 || colorType == 6) { if(bitDepth != 8 && bitDepth != 16) { throw new FormatException ("invalid depth for color type " + colorType.ToString()); } } else if(colorType == 3) { if(bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8) { throw new FormatException ("invalid depth for color type 3"); } } else { throw new FormatException("invalid color type"); } if(compressionMethod != 0) { throw new FormatException ("invalid compression method"); } if(filterMethod != 0) { throw new FormatException ("invalid filter method"); } if(interlaceMethod != 0 && interlaceMethod != 1) { throw new FormatException ("invalid interlace method"); } // Create the image frame with the requested values. if(colorType == 3) { format = PixelFormat.Format8bppIndexed; } else if((colorType & 4) != 0) { if(significant == 0x01050505 && bitDepth == 8) { format = PixelFormat.Format16bppArgb1555; } else if(bitDepth == 8) { format = PixelFormat.Format32bppArgb; } else { format = PixelFormat.Format64bppArgb; } } else if(colorType == 0 && bitDepth == 16) { format = PixelFormat.Format16bppGrayScale; } else { if(significant == 0x00050505 && bitDepth == 8) { format = PixelFormat.Format16bppRgb555; } else if(significant == 0x00050605 && bitDepth == 8) { format = PixelFormat.Format16bppRgb565; } else if(bitDepth == 8) { format = PixelFormat.Format24bppRgb; } else { format = PixelFormat.Format48bppRgb; } } image.Width = width; image.Height = height; image.PixelFormat = format; image.LoadFormat = Image.Png; frame = image.AddFrame(width, height, format); } else if(reader.Type == PLTE) { // We must have a frame at this point. if(frame == null) { throw new FormatException ("palette specified before image header"); } // The palette is only required for color type 3. // Other color types use it as a hint only. if(colorType == 3) { int[] palette = new int [256]; frame.Palette = palette; Array.Clear(buffer, 0, buffer.Length); if(reader.Length > 768) { reader.Read(buffer, 0, 768); } else { reader.Read(buffer, 0, buffer.Length); } for(index = 0; index < 256; ++index) { palette[index] = Utils.ReadRGB(buffer, index * 3); } } } else if(reader.Type == tRNS) { // We must have a frame at this point. if(frame == null) { throw new FormatException ("transparency specified before image header"); } // We only support simple transparencies for // color type 3 at present. The transparency // information is ignored for other color types. if(colorType == 3) { index = 0; while(index < 256 && reader.Length > 0) { if(reader.Read(buffer, 0, 1) != 1) { break; } if(buffer[0] < 0x80) { frame.TransparentPixel = index; break; } ++index; } } } else if(reader.Type == sBIT) { // Read the number of significant bits so that // we can detect images that started off life // as 15-bit or 16-bit RGB. if(reader.Length == 3) { reader.Read(buffer, 0, 3); significant = Utils.ReadRGB(buffer, 0); } else if(reader.Length == 4) { reader.Read(buffer, 0, 4); significant = Utils.ReadRGB(buffer, 0) | (buffer[3] << 24); } } else if(reader.Type == IDAT) { // We must have a frame at this point. if(frame == null) { throw new FormatException ("image data specified before image header"); } // There can be only one set of data chunks. if(decompress != null) { throw new FormatException ("multiple image data blocks encountered"); } // Create a zlib decompressor. decompress = new ZlibDecompressor(reader); // Get the pass processing function. passFunc = GetPassFunc(colorType, bitDepth, format); // Process the data in the image. if(interlaceMethod == 0) { // No interlacing. scanlineReader = new ScanlineReader (decompress, width, height, colorType, bitDepth); passFunc(frame, scanlineReader, width, height, 0, 0, 1, 1); } else { // Use Adam7 interlacing. for(pass = 0; pass < 7; ++pass) { // Calculate the width and height of the pass. // Please refer "PNG - The Definitive Guide" // for a totally misleading and incompatible // description of the following code - Gopal passWidth = width + adam7Rules[(pass+1) * 4 + 2] - 1; passWidth /= adam7Rules[pass * 4 + 2]; if(passWidth <= 0) { continue; } passHeight = height + adam7Rules[(pass+1) * 4 + 3 ] - 1; passHeight /= adam7Rules[pass * 4 + 3]; if(passHeight <= 0) { continue; } // Create a scanline reader for the pass. scanlineReader = new ScanlineReader (decompress, passWidth, passHeight, colorType, bitDepth); // Process the Adam7 pass. passFunc(frame, scanlineReader, passWidth, passHeight, adam7Rules[pass * 4], adam7Rules[pass * 4 + 1], adam7Rules[pass * 4 + 2], adam7Rules[pass * 4 + 3]); } } // Eat any remaining IDAT data blocks. decompress.EatRemaining(); // Skip the "Reset", because we've already done it. continue; } // Reset the chunk reader and move on to the next chunk. reader.Reset(buffer); } // Skip the contents of the IEND chunk and check its CRC. reader.Skip(buffer); // If we don't have a frame or decompressor, // then the PNG stream was empty. if(frame == null || decompress == null) { throw new FormatException("PNG did not contain an image"); } }
// Constructor. public ScanlineReader(ZlibDecompressor decompressor, int width, int height, int colorType, int bitDepth) { // Determine the number of bytes per pixel (rounded up), // and the number of bytes per scan line. switch(colorType) { case 0: { switch(bitDepth) { case 1: { bytesPerPixel = 1; bytesPerLine = (width + 7) / 8; } break; case 2: { bytesPerPixel = 1; bytesPerLine = (width + 3) / 4; } break; case 4: { bytesPerPixel = 1; bytesPerLine = (width + 1) / 2; } break; case 8: { bytesPerPixel = 1; bytesPerLine = width; } break; case 16: { bytesPerPixel = 2; bytesPerLine = width * 2; } break; } } break; case 2: { if(bitDepth == 8) { bytesPerPixel = 3; bytesPerLine = width * 3; } else { bytesPerPixel = 6; bytesPerLine = width * 6; } } break; case 3: { bytesPerPixel = 1; bytesPerLine = width; } break; case 4: { if(bitDepth == 8) { bytesPerPixel = 2; bytesPerLine = width * 2; } else { bytesPerPixel = 4; bytesPerLine = width * 4; } } break; case 6: { if(bitDepth == 8) { bytesPerPixel = 4; bytesPerLine = width * 4; } else { bytesPerPixel = 8; bytesPerLine = width * 8; } } break; } // Allocate the scanline buffers. scanline = new byte [bytesPerLine]; prevScanline = new byte [bytesPerLine]; // Initialize the other values. this.decompressor = decompressor; this.y = 0; this.height = height; }