Pretty-print.
[beagle.git] / Util / PngHeader.cs
blob71307b1a45b49c4ed476223af2586400999d63e6
1 //
2 // EndianConverter.cs
3 //
4 // Authors:
5 // Larry Ewing <lewing@novell.com>
6 //
7 //
8 // Copyright (C) 2004 - 2006 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using ICSharpCode.SharpZipLib.Zip.Compression;
31 using SemWeb;
33 namespace Beagle.Util {
34 public class PngHeader {
35 System.Collections.ArrayList chunk_list;
37 public PngHeader (System.IO.Stream stream)
39 Load (stream);
42 /**
43 Title Short (one line) title or caption for image
44 Author Name of image's creator
45 Description Description of image (possibly long)
46 Copyright Copyright notice
47 Creation Time Time of original image creation
48 Software Software used to create the image
49 Disclaimer Legal disclaimer
50 Warning Warning of nature of content
51 Source Device used to create the image
52 Comment Miscellaneous comment
54 xmp is XML:com.adobe.xmp
56 Other keywords may be defined for other purposes. Keywords of general interest can be registered with th
60 public void Select (SemWeb.StatementSink sink)
62 foreach (Chunk c in Chunks) {
63 if (c is IhdrChunk) {
64 IhdrChunk ih = c as IhdrChunk;
65 MetadataStore.AddLiteral (sink, "tiff:ImageWidth", ih.Width.ToString ());
66 MetadataStore.AddLiteral (sink, "tiff:ImageLength", ih.Height.ToString ());
67 } else if(c is TimeChunk) {
68 TimeChunk tc = c as TimeChunk;
70 MetadataStore.AddLiteral (sink, "xmp:ModifyDate", tc.Time.ToString ("yyyy-MM-ddThh:mm:ss"));
71 } else if (c is TextChunk) {
72 TextChunk text = c as TextChunk;
74 switch (text.Keyword) {
75 case "XMP":
76 case "XML:com.adobe.xmp":
77 System.IO.Stream xmpstream = new System.IO.MemoryStream (text.TextData);
78 XmpFile xmp = new XmpFile (xmpstream);
79 xmp.Select (sink);
80 break;
81 case "Comment":
82 MetadataStore.AddLiteral (sink, "exif:UserComment", text.Text);
83 break;
84 case "Software":
85 break;
86 case "Title":
87 MetadataStore.AddLiteral (sink, "dc:title", "rdf:Alt", new Literal (text.Text, "x-default", null));
88 break;
89 case "Author":
90 MetadataStore.AddLiteral (sink, "dc:creator", "rdf:Seq", new Literal (text.Text));
91 break;
92 case "Copyright":
93 MetadataStore.AddLiteral (sink, "dc:rights", "rdf:Alt", new Literal (text.Text, "x-default", null));
94 break;
95 case "Description":
96 MetadataStore.AddLiteral (sink, "dc:description", "rdf:Alt", new Literal (text.Text, "x-default", null));
97 break;
98 case "Creation Time":
99 try {
100 System.DateTime time = System.DateTime.Parse (text.Text);
101 MetadataStore.AddLiteral (sink, "xmp:CreateDate", time.ToString ("yyyy-MM-ddThh:mm:ss"));
102 } catch (System.Exception e) {
103 System.Console.WriteLine (e.ToString ());
105 break;
107 } else if (c is ColorChunk) {
108 ColorChunk color = (ColorChunk)c;
109 string [] whitepoint = new string [2];
110 whitepoint [0] = color.WhiteX.ToString ();
111 whitepoint [1] = color.WhiteY.ToString ();
112 MetadataStore.Add (sink, "tiff:WhitePoint", "rdf:Seq", whitepoint);
113 int i = 0;
114 string [] rgb = new string [6];
115 rgb [i++] = color.RedX.ToString ();
116 rgb [i++] = color.RedY.ToString ();
117 rgb [i++] = color.GreenX.ToString ();
118 rgb [i++] = color.GreenY.ToString ();
119 rgb [i++] = color.BlueX.ToString ();
120 rgb [i++] = color.BlueY.ToString ();
121 MetadataStore.Add (sink, "tiff:PrimaryChromaticities", "rdf:Seq", rgb);
122 } else if (c.Name == "sRGB") {
123 MetadataStore.AddLiteral (sink, "exif:ColorSpace", "1");
124 } else if (c is PhysChunk) {
125 PhysChunk phys = (PhysChunk)c;
126 uint denominator = (uint) (phys.InMeters ? 100 : 1);
128 MetadataStore.AddLiteral (sink, "tiff:ResolutionUnit", phys.InMeters ? "3" : "1");
129 MetadataStore.AddLiteral (sink, "tiff:XResolution", new FSpot.Tiff.Rational (phys.PixelsPerUnitX, denominator).ToString ());
130 MetadataStore.AddLiteral (sink, "tiff:YResolution", new FSpot.Tiff.Rational (phys.PixelsPerUnitY, denominator).ToString ());
136 public System.Collections.ArrayList Chunks {
137 get { return chunk_list; }
140 public class ZtxtChunk : TextChunk {
141 //public static string Name = "zTXt";
143 protected bool compressed = true;
144 public bool Compressed {
145 get {
146 return compressed;
150 byte compression;
151 public byte Compression {
152 get {
153 return compression;
155 set {
156 if (compression != 0)
157 throw new System.Exception ("Unknown compression method");
161 public ZtxtChunk (string name, byte [] data) : base (name, data) {}
163 public override void Load (byte [] data)
165 int i = 0;
166 keyword = GetString (ref i);
167 i++;
168 Compression = data [i++];
170 text_data = Chunk.Inflate (data, i, data.Length - i);
174 public class PhysChunk : Chunk {
175 public PhysChunk (string name, byte [] data) : base (name, data) {}
177 public uint PixelsPerUnitX {
178 get {
179 return EndianConverter.ToUInt32 (data, 0, false);
183 public uint PixelsPerUnitY {
184 get {
185 return EndianConverter.ToUInt32 (data, 4, false);
189 public bool InMeters {
190 get {
191 return data [8] == 0;
196 public class TextChunk : Chunk {
197 //public static string Name = "tEXt";
199 protected string keyword;
200 protected string text;
201 protected byte [] text_data;
202 protected System.Text.Encoding encoding = Latin1;
204 public static System.Text.Encoding Latin1 = System.Text.Encoding.GetEncoding (28591);
205 public TextChunk (string name, byte [] data) : base (name, data) {}
207 public override void Load (byte [] data)
209 int i = 0;
211 keyword = GetString (ref i);
212 i++;
213 int len = data.Length - i;
214 text_data = new byte [len];
215 System.Array.Copy (data, i, text_data, 0, len);
218 public string Keyword {
219 get {
220 return keyword;
224 public byte [] TextData
226 get {
227 return text_data;
231 public string Text {
232 get {
233 return encoding.GetString (text_data, 0, text_data.Length);
238 public class IccpChunk : Chunk {
239 string keyword;
240 byte [] profile;
242 public IccpChunk (string name, byte [] data) : base (name, data) {}
244 public override void Load (byte [] data)
246 int i = 0;
247 keyword = GetString (ref i);
248 i++;
249 int compression = data [i++];
250 if (compression != 0)
251 throw new System.Exception ("Unknown Compression type");
253 profile = Chunk.Inflate (data, i, data.Length - i);
256 public string Keyword {
257 get {
258 return keyword;
262 public byte [] Profile {
263 get {
264 return profile;
269 public class ItxtChunk : ZtxtChunk{
270 //public static string Name = "zTXt";
272 string Language;
273 string LocalizedKeyword;
275 public override void Load (byte [] data)
277 int i = 0;
278 keyword = GetString (ref i);
279 i++;
280 compressed = (data [i++] != 0);
281 Compression = data [i++];
282 Language = GetString (ref i);
283 i++;
284 LocalizedKeyword = GetString (ref i, System.Text.Encoding.UTF8);
285 i++;
287 if (Compressed) {
288 text_data = Chunk.Inflate (data, i, data.Length - i);
289 } else {
290 int len = data.Length - i;
291 text_data = new byte [len];
292 System.Array.Copy (data, i, text_data, 0, len);
296 public ItxtChunk (string name, byte [] data) : base (name, data)
298 encoding = System.Text.Encoding.UTF8;
302 public class TimeChunk : Chunk {
303 //public static string Name = "tIME";
305 System.DateTime time;
307 public System.DateTime Time {
308 get {
309 return new System.DateTime (EndianConverter.ToUInt16 (data, 0, false),
310 data [2], data [3], data [4], data [5], data [6]);
313 set {
314 byte [] year = EndianConverter.GetBytes ((ushort)value.Year, false);
315 data [0] = year [0];
316 data [1] = year [1];
317 data [2] = (byte) value.Month;
318 data [3] = (byte) value.Day;
319 data [4] = (byte) value.Hour;
320 data [6] = (byte) value.Minute;
321 data [7] = (byte) value.Second;
325 public TimeChunk (string name, byte [] data) : base (name, data) {}
328 public class StandardRgbChunk : Chunk {
329 public StandardRgbChunk (string name, byte [] data) : base (name, data) {}
332 public class GammaChunk : Chunk {
333 public GammaChunk (string name, byte [] data) : base (name, data) {}
334 private const int divisor = 100000;
336 public double Gamma {
337 get {
338 return EndianConverter.ToUInt32 (data, 0, false) / (double) divisor;
343 public class ColorChunk : Chunk {
344 // FIXME this should be represented like a tiff rational
345 public const uint Denominator = 100000;
347 public ColorChunk (string name, byte [] data) : base (name, data) {}
349 public FSpot.Tiff.Rational WhiteX {
350 get {
351 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 0, false), Denominator);
354 public FSpot.Tiff.Rational WhiteY {
355 get {
356 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 4, false), Denominator);
359 public FSpot.Tiff.Rational RedX {
360 get {
361 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 8, false), Denominator);
364 public FSpot.Tiff.Rational RedY {
365 get {
366 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 12, false), Denominator);
369 public FSpot.Tiff.Rational GreenX {
370 get {
371 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 16, false), Denominator);
374 public FSpot.Tiff.Rational GreenY {
375 get {
376 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 20, false), Denominator);
379 public FSpot.Tiff.Rational BlueX {
380 get {
381 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 24, false), Denominator);
384 public FSpot.Tiff.Rational BlueY {
385 get {
386 return new FSpot.Tiff.Rational (FSpot.EndianConverter.ToUInt32 (data, 28, false), Denominator);
392 public enum ColorType : byte {
393 Gray = 0,
394 Rgb = 2,
395 Indexed = 3,
396 GrayAlpha = 4,
397 RgbAlpha = 6
400 public enum CompressionMethod : byte {
401 Zlib = 0
404 public enum InterlaceMethod : byte {
405 None = 0,
406 Adam7 = 1
409 public enum FilterMethod : byte {
410 Adaptive = 0
413 // Filter Types Show up as the first byte of each scanline
414 public enum FilterType {
415 None = 0,
416 Sub = 1,
417 Up = 2,
418 Average = 3,
419 Paeth = 4
422 public class IhdrChunk : Chunk {
423 public uint Width;
424 public uint Height;
425 public byte Depth;
426 public ColorType Color;
427 public PngHeader.CompressionMethod Compression;
428 public FilterMethod Filter;
429 public InterlaceMethod Interlace;
431 public IhdrChunk (string name, byte [] data) : base (name, data) {}
433 public override void Load (byte [] data)
435 Width = EndianConverter.ToUInt32 (data, 0, false);
436 Height = EndianConverter.ToUInt32 (data, 4, false);
437 Depth = data [8];
438 Color = (ColorType) data [9];
439 //if (Color != ColorType.Rgb)
440 // throw new System.Exception (System.String.Format ("unsupported {0}", Color));
442 this.Compression = (CompressionMethod) data [10];
443 if (this.Compression != CompressionMethod.Zlib)
444 throw new System.Exception (System.String.Format ("unsupported {0}", Compression));
446 Filter = (FilterMethod) data [11];
447 if (Filter != FilterMethod.Adaptive)
448 throw new System.Exception (System.String.Format ("unsupported {0}", Filter));
450 Interlace = (InterlaceMethod) data [12];
451 //if (Interlace != InterlaceMethod.None)
452 // throw new System.Exception (System.String.Format ("unsupported {0}", Interlace));
456 public int ScanlineComponents {
457 get {
458 switch (Color) {
459 case ColorType.Gray:
460 case ColorType.Indexed:
461 return 1;
462 case ColorType.GrayAlpha:
463 return 2;
464 case ColorType.Rgb:
465 return 3;
466 case ColorType.RgbAlpha:
467 return 4;
468 default:
469 throw new System.Exception (System.String.Format ("Unknown format {0}", Color));
474 public uint GetScanlineLength (int pass)
476 uint length = 0;
477 if (Interlace == InterlaceMethod.None) {
478 int bits = ScanlineComponents * Depth;
479 length = (uint) (this.Width * bits / 8);
481 // and a byte for the FilterType
482 length ++;
483 } else {
484 throw new System.Exception (System.String.Format ("unsupported {0}", Interlace));
487 return length;
491 public class Chunk {
492 public string Name;
493 protected byte [] data;
494 protected static System.Collections.Hashtable name_table;
496 public byte [] Data {
497 get {
498 return data;
500 set {
501 Load (value);
505 static Chunk ()
508 name_table = new System.Collections.Hashtable ();
509 name_table ["iTXt"] = typeof (ItxtChunk);
510 name_table ["tXMP"] = typeof (ItxtChunk);
511 name_table ["tEXt"] = typeof (TextChunk);
512 name_table ["zTXt"] = typeof (ZtxtChunk);
513 name_table ["tIME"] = typeof (TimeChunk);
514 name_table ["iCCP"] = typeof (IccpChunk);
515 name_table ["IHDR"] = typeof (IhdrChunk);
516 name_table ["cHRM"] = typeof (ColorChunk);
517 name_table ["pHYs"] = typeof (PhysChunk);
518 name_table ["gAMA"] = typeof (GammaChunk);
519 name_table ["sRGB"] = typeof (StandardRgbChunk);
522 public Chunk (string name, byte [] data)
524 this.Name = name;
525 this.data = data;
526 Load (data);
530 protected string GetString (ref int i, System.Text.Encoding enc)
532 for (; i < data.Length; i++) {
533 if (data [i] == 0)
534 break;
537 return enc.GetString (data, 0, i);
540 protected string GetString (ref int i)
542 return GetString (ref i, TextChunk.Latin1);
545 public virtual void Load (byte [] data)
550 public bool Critical {
551 get {
552 return !System.Char.IsLower (Name, 0);
556 public bool Private {
557 get {
558 return System.Char.IsLower (Name, 1);
562 public bool Reserved {
563 get {
564 return System.Char.IsLower (Name, 2);
568 public bool Safe {
569 get {
570 return System.Char.IsLower (Name, 3);
574 public bool CheckCrc (uint crc)
576 // FIXME implement me
577 return true;
580 public static Chunk Generate (string name, byte [] data)
582 System.Type t = (System.Type) name_table [name];
584 Chunk chunk;
585 if (t != null)
586 chunk = (Chunk) System.Activator.CreateInstance (t, new object[] {name, data});
587 else
588 chunk = new Chunk (name, data);
590 return chunk;
593 public static byte [] Inflate (byte [] input, int start, int length)
595 System.IO.MemoryStream output = new System.IO.MemoryStream ();
596 Inflater inflater = new Inflater ();
598 inflater.SetInput (input, start, length);
600 byte [] buf = new byte [1024];
601 int inflate_length;
602 while ((inflate_length = inflater.Inflate (buf)) > 0) {
603 output.Write (buf, 0, inflate_length);
606 byte [] result = new byte [output.Length];
607 output.Position = 0;
608 output.Read (result, 0, result.Length);
609 output.Close ();
610 return result;
615 public class ChunkInflater {
616 private Inflater inflater;
617 private System.Collections.ArrayList chunks;
619 public ChunkInflater ()
621 inflater = new Inflater ();
622 chunks = new System.Collections.ArrayList ();
625 public bool Fill ()
627 while (inflater.IsNeedingInput && chunks.Count > 0) {
628 inflater.SetInput (((Chunk)chunks[0]).Data);
629 //System.Console.WriteLine ("adding chunk {0}", ((Chunk)chunks[0]).Data.Length);
630 chunks.RemoveAt (0);
632 return true;
635 public int Inflate (byte [] data, int start, int length)
637 int result = 0;
638 do {
639 Fill ();
640 result += inflater.Inflate (data, start + result, length - result);
641 //System.Console.WriteLine ("Attempting Second after fill Inflate {0} {1} {2}", attempt, result, length - result);
642 } while (result < length && chunks.Count > 0);
644 return result;
647 public void Add (Chunk chunk)
649 chunks.Add (chunk);
653 void Load (System.IO.Stream stream)
655 byte [] heading = new byte [8];
656 stream.Read (heading, 0, heading.Length);
658 if (heading [0] != 137 ||
659 heading [1] != 80 ||
660 heading [2] != 78 ||
661 heading [3] != 71 ||
662 heading [4] != 13 ||
663 heading [5] != 10 ||
664 heading [6] != 26 ||
665 heading [7] != 10)
666 throw new System.Exception ("Invalid PNG magic number");
668 chunk_list = new System.Collections.ArrayList ();
670 for (int i = 0; stream.Read (heading, 0, heading.Length) == heading.Length; i++) {
671 uint length = EndianConverter.ToUInt32 (heading, 0, false);
672 string name = System.Text.Encoding.ASCII.GetString (heading, 4, 4);
673 byte [] data = new byte [length];
674 if (length > 0)
675 stream.Read (data, 0, data.Length);
677 stream.Read (heading, 0, 4);
678 uint crc = EndianConverter.ToUInt32 (heading, 0, false);
680 Chunk chunk = Chunk.Generate (name, data);
681 if (! chunk.CheckCrc (crc)) {
682 if (chunk.Critical) {
683 throw new System.Exception ("Chunk CRC check failed");
684 } else {
685 System.Console.WriteLine ("bad CRC in Chunk {0}... skipping",
686 chunk.Name);
687 continue;
689 } else {
690 chunk_list.Add (chunk);
693 if (chunk.Name == "IEND")
694 break;
698 public string LookupText (string keyword)
700 TextChunk chunk = LookupTextChunk (keyword);
701 if (chunk != null)
702 return chunk.Text;
704 return null;
707 public TextChunk LookupTextChunk (string keyword)
709 foreach (Chunk chunk in Chunks) {
710 TextChunk text = chunk as TextChunk;
711 if (text != null && text.Keyword == keyword)
712 return text;
714 return null;