using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace Pgm { public enum PgmType { P2, P5 } public static class PgmCreator { const byte NEW_LINE = 10; /* ascii code of new line */ const byte HASH = 35; /* ascii code of hash: '#' */ const byte SPACE = 32; /* ascii code of the blank / space */ const byte ASCII_P = 80; const byte ASCII_2 = 50; const byte ASCII_5 = 53; #region PGM Reader /// /// Reads in a PGM file in P2 or P5 Format and returns a byte[,]-Array if everything works fine. Otherwise, exceptions are thrown. /// For the specification of PGM-Files, see: http://netpbm.sourceforge.net/doc/pgm.html /// /// This Method was used and modified from http://www.cnblogs.com/lihongsheng0217/archive/2008/08/12/1266426.html /// Original author: Mennan Tekbir(mtekbir[]gmail[]com). /// Major modifications by Radomir Dinic and Gerhard Mitterlechner. License: GPL /// /// IMPORTANT: /// *) A comment line must be present ofter the type code (P2 or P5). /// *) Also: class only handles gray-values <= 255 correctly! (according to spec. <= 65536 should be supported) /// *) Only newlines LF (ASCII 10) are used. Therefore, Windows users who create or modify the P2 PGM (=ASCII) files /// MANUALLY may run into problems, because Windows OS uses CR (ASCII 13) AND NL (ASCII 10) for a line break. /// /// /// public static byte[,] ReadPgmFile(string filename) { try { FileStream inputStream; BinaryReader pgmReader; inputStream = File.OpenRead(filename); pgmReader = new BinaryReader(inputStream); byte[] pgmDataBuffer; //the actual image-data (width*height many elements) int width, height; //dimension of the image using (inputStream) { //using two ressources, using can be nested using (pgmReader) { // stores some header info of PGM-File temporarily. // WARNING: Here, the comment can be max. 999 characters long (plus the '#')! byte[] tempArray = new byte[1000]; /* Sample PGM : (the dimension of the image is arbitrary, but the max-color value must be 255! (more strict than in specification!)) * P2 * # Created by ... * 512 512 * 255 * [data] */ //read PGM Type P2, P5 tempArray[0] = pgmReader.ReadByte(); if (tempArray[0] != ASCII_P) throw new FormatException("Wrong first byte in pgm-File."); tempArray[1] = pgmReader.ReadByte(); if (tempArray[1] != ASCII_2 && tempArray[1] != ASCII_5) throw new FormatException("Wrong second byte in pgm-File."); //make a string out of first two read ASCII-Codes: string sType = System.Text.ASCIIEncoding.Default.GetString(tempArray, 0, 2); //read until new line while (pgmReader.ReadByte() != NEW_LINE) {;} ReadComment(pgmReader, inputStream); width = Convert.ToInt32(ReadUntil(SPACE, pgmReader)); height = Convert.ToInt32(ReadUntil(NEW_LINE, pgmReader)); if (width < 1 || width > 10000 || height < 1 || height > 10000) throw new FormatException("Wrong values for width/height."); //read the maximal gray-scale color value (e.g., 255. Must be < 65536 according to the specification.) //WARNING: this programm only allows a max color value of 255!!! int maxColor = Convert.ToInt32(ReadUntil(NEW_LINE, pgmReader)); if (maxColor > 255) throw new FormatException("Max color value too high: must be <= 255."); //read image data pgmDataBuffer = new byte[width * height]; if (sType == "P5") ReadP5(pgmDataBuffer, pgmReader); else if (sType == "P2") ReadP2(pgmDataBuffer, pgmReader); } //end inner using } //end outer using return CreateTwodimensionalArray(pgmDataBuffer, width, height); } //end outer try catch (FormatException) { throw; //just rethrow it } catch (FileNotFoundException e) { //example of nested exception (same type) throw new FileNotFoundException("File " + filename + " could not be found.", e); } catch (OutOfMemoryException e) { //example of nested exceptions (different type) throw new ApplicationException("ReadPGMFile has got too little memory!", e); } catch (IOException e) { throw new Exception("ReadPGMFile produced some IO-exception: " + e.Message); } catch (Exception e) { throw new Exception("ReadPGMFile produced some exception: " + e.Message); } } //Read comments (if existI. Only one or no comment line is supported. //Only length of max. 999 characters plus the '#'! private static void ReadComment(BinaryReader pgmReader, FileStream inputStream) { try { byte tempByte = pgmReader.ReadByte(); if (tempByte == HASH) ReadUntil(NEW_LINE, pgmReader); else //no comment ==> reset cursor inputStream.Seek(-1, SeekOrigin.Current); } catch (IOException e) { throw new Exception("IO-Exception in ReadComment: " + e.Message); } catch (Exception e) { throw new Exception("Error in ReadComment: " + e.Message); } } //Reads the image data for a P5 file into buffer. private static void ReadP5(byte[] buffer, BinaryReader pgmReader) { //If file is binary, read every byte try { //store each color value: byte[] readBytes = pgmReader.ReadBytes(buffer.Length); //copy the content to the destination buffer: Array.Copy(readBytes, buffer, readBytes.Length); } catch (Exception e) { throw new Exception("Error reading binary data from P5 file! " + e.Message); } } //Reads the P2 image data to the buffer. private static void ReadP2(byte[] buffer, BinaryReader pgmReader) { //if file is text-based every pixel is distinguished by "space" //and it has up to 3 chars (max grey-value of 255!) try { byte[] tempArray = new byte[1000]; byte tempByte = pgmReader.ReadByte(); int k = 0; while (pgmReader.BaseStream.Position != pgmReader.BaseStream.Length) { //read all non-newline or non-whitespace character int i = 0; while (tempByte != NEW_LINE && tempByte != SPACE) { tempArray[i] = tempByte; i++; tempByte = pgmReader.ReadByte(); } //Convert the non-newline/non-whitespace characters to a string and //copy the string representation of every pixel to buffer of image data. buffer[k] = Convert.ToByte(System.Text.ASCIIEncoding.Default.GetString(tempArray, 0, i)); k++; tempByte = pgmReader.ReadByte(); if (pgmReader.BaseStream.Position != pgmReader.BaseStream.Length) { if (tempByte == NEW_LINE || tempByte == SPACE) { tempByte = pgmReader.ReadByte(); //read next character (must be a non-newline/non-blank) } } } //end while } //end try catch (EndOfStreamException e) { //if premature end of stream is reached... throw new Exception("Unexcpected end of stream reading P2 file: " + e.Message); } catch (IOException e) { throw new Exception("Some IO exception when reading P2 file: " + e.Message); } catch (Exception e){ throw new Exception("Some other exception when reading P2 file: " + e.Message); } } //Reads from pgmReader until separator is reached. (Max 1000 bytes.) private static string ReadUntil(byte separator, BinaryReader pgmReader) { try { byte[] tempArray = new byte[1000]; int i = 0; byte tempByte = pgmReader.ReadByte(); while (tempByte != separator) { tempArray[i] = tempByte; i++; tempByte = pgmReader.ReadByte(); } //make a string out of tempArray return System.Text.ASCIIEncoding.Default.GetString(tempArray, 0, i); } catch (IndexOutOfRangeException) { throw new IndexOutOfRangeException("ReadUntil failed: wrong indices in while-loop."); } catch (Exception e) { throw new Exception("ReadUntil failed: " + e.Message); } } //Creates a two-dim array out of the one-dim pgmDataBuffer. private static byte[,] CreateTwodimensionalArray(byte[] pgmDataBuffer, int width, int height) { try { byte[,] temp = new byte[width, height]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { temp[x, y] = pgmDataBuffer[y * width + x]; } } return temp; } catch (OutOfMemoryException e) { throw new OutOfMemoryException("CreateTwodimensionalArray failed: too little memory.", e); } catch (IndexOutOfRangeException) { throw new Exception("Wrong index in for-loop of CreateTwodimensionalArray."); } catch (Exception e) { throw new Exception("CreateTwodimensionalArray failed: " + e.Message); } } #endregion #region PGM Writer /// /// Write a given byte[,] to Pgm-File or throws an exception on an error. /// Formats: /// P2: ASCII-Format /// P5: Binary-Format /// /// filename or path /// array with image-data /// Type P2 or P5 public static void WritePgmFile(string filename, byte[,] imgData, PgmType type) { if (imgData == null) throw new ArgumentException("WritePgmFileBinary: imgData must not be null."); switch (type) { case PgmType.P2: WritePgmFileASCII(filename, imgData, type); break; case PgmType.P5: WritePgmFileBinary(filename, imgData, type); break; default: throw new ArgumentException("WritePgmFile failed: wrong type!"); } } //helper for WritePgmFile private static void WritePgmFileASCII(string filename, byte[,] imgData, PgmType type) { StreamWriter writer = null; try { writer = new StreamWriter(filename); writer.Write(BuildPgmHeader(imgData.GetLength(0), imgData.GetLength(1), type)); for (int y = 0; y < imgData.GetLength(1); y++) { for (int x = 0; x < imgData.GetLength(0); x++) { string data = imgData[x, y].ToString() + " "; writer.Write(data); } writer.Write('\n'); } } catch (UnauthorizedAccessException e) { throw new Exception("WritePgmFile failed: no permission to write. " + e.Message); } catch (Exception e) { throw new Exception("WritePgmFileASCII failed: could not write to file " + filename + ". " + e.Message); } finally { //example using finally (instead of "using") if (writer != null) writer.Close(); } } //helper for WritePgmFile private static void WritePgmFileBinary(string filename, byte[,] imgData, PgmType type) { try { FileStream outputStream = File.Create(filename); BinaryWriter pgmWriter = new BinaryWriter(outputStream); using (outputStream) { using (pgmWriter) { string PGMInfo = BuildPgmHeader(imgData.GetLength(0), imgData.GetLength(1), type); byte[] PGMInfoBuffer = System.Text.ASCIIEncoding.Default.GetBytes(PGMInfo); pgmWriter.Write(PGMInfoBuffer); byte[] temp = CreateOneDimensionalTempArray(imgData); pgmWriter.Write(temp); } } } catch (IOException e) { throw new Exception("IOException in WritePgmFileBinary writing to file: " + filename + ". " + e.Message); } catch (OutOfMemoryException e) { throw new Exception("WritePgmFile failed with OutOfMemoryException: " + e.Message); } catch (UnauthorizedAccessException e) { throw new Exception("WritePgmFile failed: no permission to write. " + e.Message); } } private static string BuildPgmHeader(int width, int height, PgmType type) { string header = string.Empty; switch (type) { case PgmType.P2: header += "P2\n"; break; case PgmType.P5: header += "P5\n"; break; default: throw new ArgumentException("Wrong type in BuildPgmHeader."); } header += "# MMT\n"; header += width + " " + height + "\n"; header += "255\n"; //warning: this is hardcoded, should allow a value up to 65535 according to specification! return header; } private static byte[] CreateOneDimensionalTempArray(byte[,] imgData) { if (imgData == null) throw new ArgumentException("CreateOneDimensionalTempArray: imgData must not be null."); int w = imgData.GetLength(0); int h = imgData.GetLength(1); try { byte[] temp = new byte[w * h]; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { temp[y * w + x] = imgData[x, y]; } } return temp; } catch (OutOfMemoryException e) { throw new OutOfMemoryException("CreateOneDimensionalTempArray failed. Too little memory! " + e.Message); } catch (Exception e) { throw new Exception("CreateOneDimensionalTempArray failed.", e); } } #endregion } }