using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TEST_DecodePng { internal class PngDecoder { const int PNG_SIGNATURE_LENGTH = 8; readonly byte[] EXPECTED_PNG_SIGNATURE = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }; const int CHUNCK_SIZE_FIELD_LENGTH = 4; const int CHUNCK_TYPE_FIELD_LENGTH = 4; const int CHUNCK_CRC_FIELD_LENGTH = 4; const int IMAGE_WIDTH_FIELD_SIZE = 4; const int IMAGE_HEIGHT_FIELD_SIZE = 4; const int GAMA_FILED_LENGTH = 4; //Critical chunks const string FIRST_CHUNK_TYPE = "IHDR"; const string LAST_CHUNCK_TYPE = "IEND"; const string DATA_CHUNCK_TYPE = "IDAT"; const string GAMA_CHUNCK_TYPE = "GAMA"; public Stopwatch chrono; public PngDecoder() { chrono = new Stopwatch(); //I start and stop the chronometer so all the methods can just use the Restart method. chrono.Start(); chrono.Stop(); } public Bitmap Decode(string filename) { //Timing Stuff chrono.Reset(); chrono.Start(); //Timing Stuff byte[] content = File.ReadAllBytes(filename); int cursor = 0; byte[] pngSignature = new byte[PNG_SIGNATURE_LENGTH]; for (int i = 0; i < PNG_SIGNATURE_LENGTH; i++) { pngSignature[i] = content[cursor]; cursor++; } //Checks if the file has a valid png signature if (!compareByteArrays(EXPECTED_PNG_SIGNATURE, pngSignature)) throw (new InvalidPngHeadersFileException("File signature not recognized")); List chuncks = new List(); List chuncksType = new List(); List chuncksSize = new List(); int chunckSize = 0; string chunckType = ""; while (chunckType.ToLower() != LAST_CHUNCK_TYPE.ToLower()) { //Reading Chunck Size (Carefull, does not contain the size, type and crc bytes) byte[] ckln = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] }; Array.Reverse(ckln); chunckSize = BitConverter.ToInt32(ckln, 0); cursor += CHUNCK_SIZE_FIELD_LENGTH; //Reading the chunck type byte[] cktp = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] }; chunckType = System.Text.Encoding.UTF8.GetString(cktp); cursor += CHUNCK_TYPE_FIELD_LENGTH; //Reading the chunck data byte[] chunck = new byte[chunckSize]; for (int i = 0; i < chunckSize; i++) { chunck[i] = content[cursor]; cursor++; } //Reading the CRC byte[] ckcrc = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] }; cursor += CHUNCK_CRC_FIELD_LENGTH; //PROCESS THE CRC chuncks.Add(chunck); chuncksSize.Add(chunckSize); chuncksType.Add(chunckType); } //Now we have a list of all the chuncks in the PNG file //We can now process it //The First chunck MUST be a IHDR chunck so we test that if (chuncksType[0].ToLower() != FIRST_CHUNK_TYPE.ToLower()) throw new InvalidPngHeadersFileException("Critial chunck not found : IHDR"); //The Last chunck MUST be an IEND chunck so we test that if (chuncksType[chuncksType.Count - 1].ToLower() != LAST_CHUNCK_TYPE.ToLower()) throw new InvalidPngHeadersFileException("Critical chunck not found : IEND"); //There must be at least one IDAT chunck so we check that int dataChunckcount = 0; foreach(string cktype in chuncksType) { if (cktype.ToLower() == DATA_CHUNCK_TYPE.ToLower()) dataChunckcount++; } if (dataChunckcount == 0) throw new InvalidPngHeadersFileException("No data chuncks where found"); PngMetaData header = null; float gama = 0; //Now we can process them for (int chunckCounter = 0; chunckCounter < chuncks.Count; chunckCounter++) { string cktype = chuncksType[chunckCounter]; switch (cktype.ToUpper()) { //IHDR case FIRST_CHUNK_TYPE: int chunckCursor = 0; byte[] btaImageWidth = new byte[IMAGE_WIDTH_FIELD_SIZE]; byte[] btaImageHeight = new byte[IMAGE_HEIGHT_FIELD_SIZE]; for (int i = 0; i < IMAGE_WIDTH_FIELD_SIZE; i++) { btaImageWidth[i] = chuncks[chunckCounter][chunckCursor]; chunckCursor++; } for (int i = 0; i < IMAGE_HEIGHT_FIELD_SIZE; i++) { btaImageHeight[i] = chuncks[chunckCounter][chunckCursor]; chunckCursor++; } //Those are in little endien notation so we need to reverse it Array.Reverse(btaImageWidth); Array.Reverse(btaImageHeight); int imageWidth = BitConverter.ToInt32(btaImageWidth, 0); int imageHeight = BitConverter.ToInt32(btaImageHeight, 0); int bitDepth = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]); chunckCursor++; int colorType = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]); chunckCursor++; int compressionMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]); chunckCursor++; int filterMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]); chunckCursor++; int interlaceMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]); chunckCursor++; header = new PngMetaData(imageHeight, imageWidth, (sbyte)bitDepth, (sbyte)colorType, (sbyte)compressionMethod, (sbyte)filterMethod, (sbyte)interlaceMethod); if (!header.IsValid()) throw new InvalidPngHeadersFileException("Header invalide"); break; case GAMA_CHUNCK_TYPE: byte[] btGamma = new byte[GAMA_FILED_LENGTH]; //btGamma = new byte[] { chuncks[chunckCounter][0], chuncks[chunckCounter][1], chuncks[chunckCounter][2], chuncks[chunckCounter][3] }; for (int i = 0; i < GAMA_FILED_LENGTH; i++) { btGamma[i] = chuncks[chunckCounter][i]; } //btGamma.Reverse(); Array.Reverse(btGamma); gama = ((float)BitConverter.ToInt32(btGamma,0) / 1000F); break; default: //Unrecognized chunck, as said in the doc should not stop the decoding break; } } if (header != null) { MessageBox.Show("Image width: " + header.Width + Environment.NewLine + "Image height: " + header.Height + Environment.NewLine + "byte depth : " + header.BitDepth + Environment.NewLine + "color type : " + header.ColorType + Environment.NewLine + "compression method : " + header.CompressionMethod + Environment.NewLine + "filter method : " + header.FilterMethod + Environment.NewLine + "interface method : " + header.InterlaceMethod + Environment.NewLine + "gama : " + gama ); } //Timing Stuff chrono.Stop(); //Timing Stuff //TO REMOVE return new Bitmap(100, 100); } public bool compareByteArrays(byte[] firstArray, byte[] secondArray) { if (firstArray.Length != secondArray.Length) return false; bool result = true; for (int i = 0; i < firstArray.Length; i++) { if (firstArray[0] != secondArray[0]) result = false; } return result; } } }