/// <summary> /// Does the remaining work to convert the byte[] containing the image into a Color[] containing colors /// for each pixel. This operation is different for most individual combinations of ColorType and bitDepth /// and currently only ColorType 2 is supported. /// </summary> /// <param name="image"></param> /// <param name="header"></param> /// <param name="palette"></param> /// <returns></returns> private static Color[] BuildPixels(byte[] image, ref HeaderInfo header, ref PaletteInfo palette) { //System.Diagnostics.Trace.WriteLine(String.Format("ColorType: {0}\tBitDepth: {1}", header.ColorType, header.BitDepth)); // System.Diagnostics.Trace.WriteLine(String.Format("Width: {0}\tHeight: {1}", header.Width, header.Height)); int totalPixels = (int)(header.Width * header.Height); int currentIndex = 0; if (totalPixels > mStaticPixels.Length) { mStaticPixels = new Color[totalPixels]; } //MemoryStream imageStream = new MemoryStream(image); int indexInImage = 0; int filterByte; switch ((int)header.ColorType) { //Each pixel is an R,G,B triple case 2: { //This section of code containing conversion of 16-bit samples is currently untested. if (header.BitDepth > 8) { int bps = (int)(header.Width * header.BitDepth * 3 + 1); //int R = 0; //int G = 0; //int B = 0; while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { mStaticPixels[currentIndex].R = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].G = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].B = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].A = byte.MaxValue; currentIndex++; } } } else { while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { mStaticPixels[currentIndex].R = image[indexInImage++]; mStaticPixels[currentIndex].G = image[indexInImage++]; mStaticPixels[currentIndex].B = image[indexInImage++]; mStaticPixels[currentIndex].A = byte.MaxValue; currentIndex++; } } } break; } //Each pixel is a grayscale sample case 0: { if (header.BitDepth < 8) { byte sample; int value; float grayValue; int numBits = 8 / header.BitDepth; int mask = (255 >> (8 - header.BitDepth)); while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width / numBits; ++i) { value = image[indexInImage++]; for (int j = 8 - header.BitDepth; j >= 0; j -= header.BitDepth) { grayValue = (float)((value & (mask << j)) >> j); sample = (byte)((grayValue / ((1 << header.BitDepth) - 1)) * 255); mStaticPixels[currentIndex].R = sample; mStaticPixels[currentIndex].G = sample; mStaticPixels[currentIndex].B = sample; mStaticPixels[currentIndex].A = byte.MaxValue; currentIndex++; } } } } else if (header.BitDepth == 8) { byte sample; float value; //Get the filter byte out of the way while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { //Read the scanline for (int i = 0; i < header.Width; ++i) { value = (float)(image[indexInImage++]); sample = (byte)((value / ((1 << header.BitDepth) - 1)) * 255); mStaticPixels[currentIndex].R = sample; mStaticPixels[currentIndex].G = sample; mStaticPixels[currentIndex].B = sample; mStaticPixels[currentIndex].A = byte.MaxValue; currentIndex++; } } } else { byte sample; while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { sample = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].R = sample; mStaticPixels[currentIndex].G = sample; mStaticPixels[currentIndex].B = sample; mStaticPixels[currentIndex].A = byte.MaxValue; currentIndex++; } } } break; } //Each pixel is a palette index case 3: { RGB entry; int value; int numBits = 8 / header.BitDepth; int mask = (255 >> (8 - header.BitDepth)); int index; long headerWidthDividedByNumBits = header.Width / numBits; while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < headerWidthDividedByNumBits; ++i) { value = image[indexInImage++]; for (int j = 8 - header.BitDepth; j >= 0; j -= header.BitDepth) { index = (value & (mask << j)) >> j; entry = palette.Entries[index]; mStaticPixels[currentIndex].R = entry.R; mStaticPixels[currentIndex].G = entry.G; mStaticPixels[currentIndex].B = entry.B; //if (mMaxTransIndex >= index) { mStaticPixels[currentIndex].A = mTransparentEntries[index]; } currentIndex++; } } } break; } //Each pixel is a grayscale sample, followed by an alpha sample case 4: { switch ((int)header.BitDepth) { case 8: { //Open stream to image array byte sample; //Get the filter byte out of the way while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { //Read the scanline for (int i = 0; i < header.Width; ++i) { sample = image[indexInImage++]; mStaticPixels[currentIndex].R = sample; mStaticPixels[currentIndex].G = sample; mStaticPixels[currentIndex].B = sample; mStaticPixels[currentIndex].A = image[indexInImage++]; currentIndex++; } } break; } case 16: { byte sample; while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { sample = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].R = sample; mStaticPixels[currentIndex].G = sample; mStaticPixels[currentIndex].B = sample; mStaticPixels[currentIndex].A = image[indexInImage++]; indexInImage++; currentIndex++; } } break; } } break; } //Each pixel is an R,G,B triple, followed by an alpha sample case 6: { switch ((int)header.BitDepth) { case 8: { while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { mStaticPixels[currentIndex].R = image[indexInImage++]; mStaticPixels[currentIndex].G = image[indexInImage++]; mStaticPixels[currentIndex].B = image[indexInImage++]; mStaticPixels[currentIndex].A = image[indexInImage++]; currentIndex++; } } break; } case 16: { int bps = (int)(header.Width * header.BitDepth * 3 + 1); //int R = 0; //int G = 0; //int B = 0; //int A = 0; while ((filterByte = image[indexInImage++]) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { mStaticPixels[currentIndex].R = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].G = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].B = image[indexInImage++]; indexInImage++; mStaticPixels[currentIndex].A = image[indexInImage++]; indexInImage++; currentIndex++; } } break; } } break; } } //imageStream.Close(); return mStaticPixels; }
/// <summary> /// Takes a List of all of the Chunks in the .png image being loaded (after IHDR is loaded and removed) /// and processes each Chunk appropriately before returning the compressed image. NOTE: Currently only /// processes the required Chunks PLTE, IDAT and IEND. Ancillary chunks are currently unsupported. /// </summary> /// <param name="chunks">The List of chunks loaded from the .png image file</param> /// <param name="header">The HeaderInfo loaded from the IHDR chunk</param> /// <param name="palette">An unused PaletteInfo struct to load the PLTE chunk data into</param> /// <returns>A byte[] containing the constructed and decompressed image</returns> private static byte[] ProcessChunks(List<Chunk> chunks, HeaderInfo header, ref PaletteInfo palette) { int numberOfBytesRead = 0; foreach (Chunk currentChunk in chunks) { switch (currentChunk.Type) { //Only one IHDR can exist, and it should have already been removed case ChunkTypes.IHDR: { throw new Exception(String.Format("List<Chunk> chunks still contains IHDR chunk info.")); //break; } case ChunkTypes.PLTE: { //Check if a palette can even be used for this image's ColorType if ((header.ColorType == 0) || (header.ColorType == 4)) throw new Exception(String.Format( "The given ColorType, {0}, does not support the use of a Palette.", header.ColorType)); //if so, prepare the palette to hold the maximum number of colors for the given bitDepth palette.Entries = new RGB[(1 << (header.BitDepth + 1)) - 1]; //Read the PLTE chunk into the given PaletteInfo ReadPalette(currentChunk, ref palette); break; } case ChunkTypes.IDAT: { //Read the IDAT chunk's data into the List for imageData ReadImageData(currentChunk, ref sReadImageDataByteBuffer, ref numberOfBytesRead); break; } case ChunkTypes.IEND: { //This should be the last chunk to be loaded, so it's safe now to decompress the image //and do any extra processing caused by ancillary chunks. //Check if there are any chunks after IEND if (chunks.IndexOf(currentChunk) != chunks.Count - 1) throw new Exception(String.Format( "IEND chunk does not appear to be the last chunk in the image.")); //Load the compressed image into an InflaterInputStream to inflate(decompress) the image MemoryStream streamForInflater = new MemoryStream(sReadImageDataByteBuffer, 0, numberOfBytesRead); InflaterInputStream decompressionStream = new InflaterInputStream(streamForInflater); //Read the decompressed image through the stream and into a byte[] ReadStream(ref decompressionStream); break; } case ChunkTypes.tRNS: { mMaxTransIndex = (int)currentChunk.Length - 1; for (int i = 0; i < currentChunk.Length; ++i) { mTransparentEntries[i] = (byte)currentChunk.ChunkData.ReadByte(); } break; } //Represents an ancillary chunk that isn't yet implemented or an unknown chunk type case ChunkTypes.UNSUPPORTED: { break; } } } //Return the image return sByteBuffer; }
/// <summary> /// Loads a .png image from the fileName given and returns an array of pixel colors. /// </summary> /// <param name="fileName">The name of the .png image to be loaded.</param> /// <returns> /// Returns a Microsoft.Xna.Framework.Graphics.Color[] containing one item(Color) for each pixel /// in the image.</returns> public static ImageData GetPixelData(string fileName) { for (int i = 0; i < ByteBufferSize; i++) { mTransparentEntries[i] = byte.MaxValue; } int bytesRead = LoadFile(fileName); mMaxTransIndex = -1; //Load the file into "byte stream" Stream sourceStream = new MemoryStream(sByteBuffer, 0, bytesRead); //Check the signature to verify this is an actual .png image if (!CheckSignature(ref sourceStream)) throw new ArgumentException( String.Format("Argument Stream {0} does not contain a valid PNG file.", sourceStream)); //Since all data in .png files is organized into categorized chunks, create a //List to store them in so that they can be read and processed later. List<Chunk> chunks = new List<Chunk>(); //Load each Chunk of data from the stream into the List of Chunks. Then //close the stream. while (GetNextChunk(ref sourceStream, ref chunks)) { } sourceStream.Close(); //Read and store the information from the IHDR chunk, which contains //general info for this image. Note: IHDR chunk is removed from List<Chunk> chunks. HeaderInfo header = ReadHeader(ref chunks); //Create an empty palette in case we need it PaletteInfo palette = new PaletteInfo(); //Process the Chunks of data and obtain the decompressed bytes of the image byte[] filteredImage = ProcessChunks(chunks, header, ref palette); //Reverse the filtering that was done on the image before compression byte[] defilteredImage = ReverseFiltering(filteredImage, header); //Translate the un-filtered image bytes into Colors and store in the array to be returned. Color[] pixelData = BuildPixels(defilteredImage, ref header, ref palette); //System.Diagnostics.Trace.WriteLine(pixelData.Length); ImageData imageData = new ImageData((int)header.Width, (int)header.Height, pixelData); return imageData; }
/// <summary> /// Processes the PLTE chunk by reading in all of the indexed color values and storing them in /// a PaletteInfo struct. /// </summary> /// <param name="plte">The Chunk containing the PLTE chunk from the file</param> /// <param name="palette">An unused PaletteInfo struct to be filled by data from plte</param> private static void ReadPalette(Chunk plte, ref PaletteInfo palette) { //The length of PLTE's data segment should be 3 * the number of entries uint numEntries = (uint)plte.ChunkData.Length / 3; //Checking for monkey-business??? if ((numEntries * 3) == plte.ChunkData.Length) { //Read in the values of the color entries and store them. for (int i = 0; i < numEntries; ++i) { RGB value = new RGB(); value.R = (byte)plte.ChunkData.ReadByte(); //System.Diagnostics.Trace.WriteLine(value.R); value.G = (byte)plte.ChunkData.ReadByte();// System.Diagnostics.Trace.WriteLine(value.G); value.B = (byte)plte.ChunkData.ReadByte();// System.Diagnostics.Trace.WriteLine(value.B); palette.Entries[i] = value; } } }
private static Color[] OldMethod(byte[] image, ref HeaderInfo header, ref PaletteInfo palette) { int totalPixels = (int)(header.Width * header.Height); int currentIndex = 0; if (totalPixels > mStaticPixels.Length) { mStaticPixels = new Color[totalPixels]; } MemoryStream imageStream = new MemoryStream(image); int filterByte; switch ((int)header.ColorType) { //Each pixel is an R,G,B triple case 2: { //This section of code containing conversion of 16-bit samples is currently untested. if (header.BitDepth > 8) { int bps = (int)(header.Width * header.BitDepth * 3 + 1); int R = 0; int G = 0; int B = 0; while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { R = imageStream.ReadByte(); imageStream.ReadByte(); G = imageStream.ReadByte(); imageStream.ReadByte(); B = imageStream.ReadByte(); imageStream.ReadByte(); mStaticPixels[currentIndex] = (new Color((byte)R, (byte)G, (byte)B)); currentIndex++; } } } else { while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { mStaticPixels[currentIndex] = (new Color( (byte)imageStream.ReadByte(), (byte)imageStream.ReadByte(), (byte)imageStream.ReadByte())); currentIndex++; } } } break; } //Each pixel is a grayscale sample case 0: { if (header.BitDepth < 8) { byte sample; int value; float grayValue; int numBits = 8 / header.BitDepth; int mask = (255 >> (8 - header.BitDepth)); while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width / numBits; ++i) { value = imageStream.ReadByte(); for (int j = 8 - header.BitDepth; j >= 0; j -= header.BitDepth) { grayValue = (float)((value & (mask << j)) >> j); sample = (byte)((grayValue / ((1 << header.BitDepth) - 1)) * 255); mStaticPixels[currentIndex] = (new Color(sample, sample, sample)); currentIndex++; } } } } else if (header.BitDepth == 8) { byte sample; float value; //Get the filter byte out of the way while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { //Read the scanline for (int i = 0; i < header.Width; ++i) { value = (float)(imageStream.ReadByte()); sample = (byte)((value / ((1 << header.BitDepth) - 1)) * 255); mStaticPixels[currentIndex] = (new Color(sample, sample, sample)); currentIndex++; } } } else { byte sample; while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { sample = (byte)imageStream.ReadByte(); imageStream.ReadByte(); mStaticPixels[currentIndex] = (new Color(sample, sample, sample)); currentIndex++; } } } break; } //Each pixel is a palette index case 3: { RGB entry; int value; int numBits = 8 / header.BitDepth; int mask = (255 >> (8 - header.BitDepth)); int index; while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width / numBits; ++i) { value = imageStream.ReadByte(); for (int j = 8 - header.BitDepth; j >= 0; j -= header.BitDepth) { index = (value & (mask << j)) >> j; entry = palette.Entries[index]; mStaticPixels[currentIndex] = (new Color((byte)entry.R, (byte)entry.G, (byte)entry.B)); if (mMaxTransIndex >= index) { mStaticPixels[currentIndex].A = mTransparentEntries[index]; } currentIndex++; } } } break; } //Each pixel is a grayscale sample, followed by an alpha sample case 4: { switch ((int)header.BitDepth) { case 8: { //Open stream to image array byte sample; //Get the filter byte out of the way while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { //Read the scanline for (int i = 0; i < header.Width; ++i) { sample = (byte)(imageStream.ReadByte()); mStaticPixels[currentIndex] = (new Color(sample, sample, sample, (byte)imageStream.ReadByte())); currentIndex++; } } break; } case 16: { byte sample; while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { sample = (byte)imageStream.ReadByte(); imageStream.ReadByte(); mStaticPixels[currentIndex] = (new Color(sample, sample, sample, (byte)imageStream.ReadByte())); imageStream.ReadByte(); currentIndex++; } } break; } } break; } //Each pixel is an R,G,B triple, followed by an alpha sample case 6: { switch ((int)header.BitDepth) { case 8: { while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { mStaticPixels[currentIndex] = (new Color( (byte)imageStream.ReadByte(), (byte)imageStream.ReadByte(), (byte)imageStream.ReadByte(), (byte)imageStream.ReadByte())); currentIndex++; } } break; } case 16: { int bps = (int)(header.Width * header.BitDepth * 3 + 1); int R = 0; int G = 0; int B = 0; int A = 0; while ((filterByte = imageStream.ReadByte()) > -1 && currentIndex < totalPixels) { for (int i = 0; i < header.Width; ++i) { R = imageStream.ReadByte(); imageStream.ReadByte(); G = imageStream.ReadByte(); imageStream.ReadByte(); B = imageStream.ReadByte(); imageStream.ReadByte(); A = imageStream.ReadByte(); imageStream.ReadByte(); mStaticPixels[currentIndex] = (new Color((byte)R, (byte)G, (byte)B, (byte)A)); currentIndex++; } } break; } } break; } } imageStream.Close(); return mStaticPixels; }