using System; namespace FSpot.Ciff { public enum Tag { // Byte valuesad NullRecord = 0x0000, FreeBytes = 0x0001, CanonColorInfo1 = 0x0032, // ASCII strings CanonFileDescription = 0x0805, UserComment = 0x0805, // FIXME this is the same as above, is it correct? CanonRawMakeModel = 0x080a, CanonFirmwareVersion = 0x080b, ComponentVersion = 0x08c, ROMOperationMode = 0x08d, OwnerName = 0x0810, CanonImageType = 0x0815, OriginalFileName = 0x0816, ThumbnailFileName = 0x0817, // Short values TargetImageType = 0x100a, ShutterReleaseMethod = 0x1010, ShutterReleaseTiming = 0x1011, ReleaseSetting = 0x1016, BaseISO = 0x101c, Uknown2 = 0x1028, FocalLength = 0x1029, CanonShotInfo = 0x102a, CanonColorInfo2 = 0x102c, CanonCameraSettings = 0x102d, WhiteSample = 0x1030, SensorInfo = 0x1031, CanonCustomFunctions = 0x1033, CanonPictureInfo = 0x1038, Unknown3 = 0x1039, Unknown4 = 0x1093, Unknown5 = 0x10a8, WhiteBalanceTable = 0x10a9, Unknown6 = 0x10aa, ColorTemperature = 0x10ae, ColorSapce = 0x10b4, Unknown7 = 0x10b5, unknown8 = 0x10c0, Unknown9 = 0x10c1, ImageFormat = 0x1803, RecordID = 0x1804, SelfTimerTime = 0x1806, TargetDistanceSetting = 0x1807, SerialNumber = 0x180b, TimeStamp = 0x180e, ImageSpec = 0x1810, FlashInfo = 0x1813, MeasuredEV = 0x1814, FileNumber = 0x1817, ExposureInfo = 0x1818, Unknown10 = 0x1834, DecoderTable = 0x1835, Unknown11 = 0x183b, // Image Data RawData = 0x2005, JpgFromRaw = 0x2007, ThumbnailImage = 0x2008, // Directories ImageDescrption = 0x2804, CameraObject = 0x2807, ShootingRecord = 0x3002, MeasuredInfo = 0x3003, CameraSpecification = 0x3004, ImageProps = 0x300a, ExifInformation = 0x300b } public struct ImageSpec { public uint ImageWidth; // Number of horizontal pixels public uint ImageHeight; // Number of vertical pixels public float PixelAspectRatio; public int RotationAngle; // degrees counter clockwise to rotate (orientation) public uint ComponentBitDepth; // bits per component public uint ColorBitDepth; // bits per component * channels public uint ColorBW; // byte wise: 0 gray - 1 color ; byte 2 use aspect ratio ; 3 and 4 reserved public ImageSpec (byte [] data, bool little) { ImageWidth = BitConverter.ToUInt32 (data, 0, little); ImageHeight = BitConverter.ToUInt32 (data, 4, little); PixelAspectRatio = BitConverter.ToSingle (data, 8, little); RotationAngle = BitConverter.ToInt32 (data, 12, little); ComponentBitDepth = BitConverter.ToUInt32 (data, 16, little); ColorBitDepth = BitConverter.ToUInt32 (data, 20, little); ColorBW = BitConverter.ToUInt32 (data, 24, little); System.Console.WriteLine ("0x{0}", ColorBW.ToString ("x")); } public PixbufOrientation Orientation { get { int angle = RotationAngle % 360; if (angle < 45) return PixbufOrientation.TopLeft; else if (angle < 135) return PixbufOrientation.RightTop; else if (angle < 225) return PixbufOrientation.BottomRight; else if (angle < 315) return PixbufOrientation.LeftBottom; else return PixbufOrientation.TopLeft; } } public bool IsColor { get { return (ColorBW & 1) > 0; } } public bool HasSquarePixels { get { return (ColorBW & 1 << 1) == 0; } } } public struct CaptureTime { uint seconds; int tz_offset; uint tz_data; public CaptureTime (byte [] data, bool little) { seconds = BitConverter.ToUInt32 (data, 0, little); tz_offset = BitConverter.ToInt32 (data, 4, little); tz_data = BitConverter.ToUInt32 (data, 8, little); } public System.DateTime LocalTime { get { return new System.DateTime (1970, 1, 1).AddSeconds (seconds); } } public bool HasTimezone { get { return ((1 << 31 & tz_data) > 0); } } public override string ToString () { string tz = String.Empty; if (HasTimezone) if (tz_offset != 0) tz = System.String.Format ("{0}{1}:{2}", seconds > 0 ? "+" : "-", tz_offset / 3600, tz_offset / 60 % 60); else tz = "Z"; return System.String.Format ("{0}{1}", LocalTime.ToString ("yyyy-MM-ddThh:mm:ss"), tz); } } public enum EntryType : ushort { Byte = 0x0000, Ascii = 0x0800, Short = 0x1000, Int = 0x1800, Struct = 0x2000, Directory1 = 0x2800, Directory2 = 0x2800, } public enum Mask { StorageFormat = 0xc000, Type = 0x3800, ID = 0x07ff, } /* See http://www.sno.phy.queensu.ca/~phil/exiftool/canon_raw.html */ public struct Entry { internal Tag Tag; internal uint Size; internal uint Offset; public Entry (byte [] data, int pos, bool little) { Tag = (Tag) BitConverter.ToUInt16 (data, pos, little); Size = BitConverter.ToUInt32 (data, pos + 2, little); Offset = BitConverter.ToUInt32 (data, pos + 6, little); } public EntryType Type { get { return GetType (Tag); } } public static EntryType GetType (Tag tag) { EntryType type = (EntryType) ((ushort)tag & (ushort)Mask.Type); return type; } public static bool IsDirectory (Tag tag) { EntryType type = GetType (tag); return (type == EntryType.Directory1 || type == EntryType.Directory2); } } public class ImageDirectory { System.Collections.ArrayList entry_list; uint Count; bool little; uint start; long DirPosition; System.IO.Stream stream; public ImageDirectory (System.IO.Stream stream, uint start, long end, bool little) { this.start = start; this.little = little; this.stream = stream; entry_list = new System.Collections.ArrayList (); stream.Position = end - 4; byte [] buf = new byte [10]; stream.Read (buf, 0, 4); uint directory_pos = BitConverter.ToUInt32 (buf, 0, little); DirPosition = start + directory_pos; stream.Position = DirPosition; stream.Read (buf, 0, 2); Count = BitConverter.ToUInt16 (buf, 0, little); for (int i = 0; i < Count; i++) { stream.Read (buf, 0, 10); System.Console.WriteLine ("reading {0} {1}", i, stream.Position); Entry entry = new Entry (buf, 0, little); entry_list.Add (entry); } } public void Dump () { System.Console.WriteLine ("Dumping directory with {0} entries", entry_list.Count); for (int i = 0; i < entry_list.Count; i++) { Entry e = (Entry) entry_list[i]; System.Console.WriteLine ("\tentry[{0}] = {1}.{6}.{5}({4}).{2}-{3}", i, e.Tag, e.Size, e.Offset, e.Tag.ToString ("x"), (uint)e.Tag & ~(uint)Mask.StorageFormat, e.Type); } } public ImageDirectory ReadDirectory (Tag tag) { foreach (Entry e in entry_list) { if (e.Tag == tag) { uint subdir_start = this.start + e.Offset; ImageDirectory subdir = new ImageDirectory (stream, subdir_start, subdir_start + e.Size, little); return subdir; } } return null; } public byte [] ReadEntry (int pos) { Entry e = (Entry) entry_list [pos]; stream.Position = this.start + e.Offset; byte [] data = new byte [e.Size]; stream.Read (data, 0, data.Length); return data; } public byte [] ReadEntry (Tag tag) { int pos = 0; foreach (Entry e in entry_list) { if (e.Tag == tag) return ReadEntry (pos); pos++; } return null; } } public class CiffFile : FSpot.ImageFile , SemWeb.StatementSource { public ImageDirectory root; private uint version; bool little; System.IO.Stream stream; // False seems a safe default public bool Distinct { get { return false; } } public ImageDirectory Root { get { if (root == null) { stream = Open (); root = Load (stream); } return root; } } public CiffFile (Uri uri) : base (uri) { } public void Select (SemWeb.StatementSink sink) { byte [] data = null; ImageDirectory props = Root.ReadDirectory (Tag.ImageProps); ImageDirectory camera = props.ReadDirectory (Tag.CameraObject); data = props.ReadEntry (Tag.TimeStamp); if (data != null) MetadataStore.AddLiteral (sink, "xmp:CreateDate", new CaptureTime (data, little).ToString ()); data = props.ReadEntry (Tag.ImageSpec); if (data != null) { ImageSpec spec = new ImageSpec (data, little); MetadataStore.AddLiteral (sink, "tiff:Orientation", ((int)spec.Orientation).ToString ()); MetadataStore.AddLiteral (sink, "tiff:ImageWidth", spec.ImageWidth.ToString ()); MetadataStore.AddLiteral (sink, "tiff:ImageLength", spec.ImageHeight.ToString ()); string comp = spec.ComponentBitDepth.ToString (); if (spec.IsColor) { MetadataStore.Add (sink, "tiff:BitsPerSample", "rdf:Seq", new string [] { comp, comp, comp }); } else { MetadataStore.Add (sink, "tiff:BitsPerSample", "rdf:Seq", new string [] { comp }); } if (!spec.HasSquarePixels) { MetadataStore.AddLiteral (sink, "tiff:XResolution", (1000000 * spec.PixelAspectRatio).ToString ()); MetadataStore.AddLiteral (sink, "tiff:YResolution", (1000000 * (1 / spec.PixelAspectRatio)).ToString ()); } } data = camera.ReadEntry (Tag.CanonRawMakeModel); if (data != null) { string make_model = System.Text.Encoding.ASCII.GetString (data, 0, data.Length - 1); string [] vals = make_model.Split (new char [] {'\0'}); MetadataStore.AddLiteral (sink, "tiff:Make", vals [0]); MetadataStore.AddLiteral (sink, "tiff:Model", vals [1]); } /* // FIXME this doesn't appear to be ascii. data = camera.ReadEntry (Tag.OwnerName); if (data != null) { string name = System.Text.Encoding.ASCII.GetString (data, 0, data.Length - 1); MetadataStore.AddLiteral (sink, "dc:creator", "rdf:Seq", new SemWeb.Literal (name)); } */ } protected ImageDirectory Load (System.IO.Stream stream) { byte [] header = new byte [26]; // the spec reserves the first 26 bytes as the header block stream.Read (header, 0, header.Length); uint start; little = (header [0] == 'I' && header [1] == 'I'); start = BitConverter.ToUInt32 (header, 2, little); // HEAP is the type CCDR is the subtype if (System.Text.Encoding.ASCII.GetString (header, 6, 8) != "HEAPCCDR") throw new ImageFormatException ("Invalid Ciff Header Block"); version = BitConverter.ToUInt32 (header, 14, little); // long end = stream.Length; return new ImageDirectory (stream, start, end, little); } public uint Version { get { return version; } } public override PixbufOrientation GetOrientation () { PixbufOrientation orientation = PixbufOrientation.TopLeft; ImageDirectory props = Root.ReadDirectory (Tag.ImageProps); byte [] data = props.ReadEntry (Tag.ImageSpec); if (data != null) orientation = new ImageSpec (data, little).Orientation; else System.Console.WriteLine ("NO ORIENTATION"); return orientation; } public override System.DateTime Date { get { ImageDirectory props = Root.ReadDirectory (Tag.ImageProps); byte [] date = props.ReadEntry (Tag.TimeStamp); if (date == null) { System.Console.WriteLine ("NO DATE"); return base.Date; } return new CaptureTime (date, little).LocalTime.ToUniversalTime (); } } public override System.IO.Stream PixbufStream () { byte [] data = GetEmbeddedJpeg (); if (data != null) return new System.IO.MemoryStream (data); else return DCRawFile.RawPixbufStream (uri); } public override Gdk.Pixbuf Load (int width, int height) { Gdk.Pixbuf full = this.Load (); Gdk.Pixbuf scaled = PixbufUtils.ScaleToMaxSize (full, width, height); full.Dispose (); return scaled; } public void Dump () { Root.Dump (); ImageDirectory props = Root.ReadDirectory (Tag.ImageProps); props.Dump (); /* string path = "out2.jpg"; System.IO.File.Delete (path); System.IO.Stream output = System.IO.File.Open (path, System.IO.FileMode.OpenOrCreate); byte [] data = GetEmbeddedThumbnail (); System.Console.WriteLine ("data length {0}", data != null ? data.Length : -1); output.Write (data, 0, data.Length); output.Close (); */ } public byte [] GetEmbeddedJpeg () { return Root.ReadEntry (Tag.JpgFromRaw); } public byte [] GetEmbeddedThumbnail () { return Root.ReadEntry (Tag.ThumbnailImage); } /* public static void Main (string [] args) { CiffFile ciff = new CiffFile (args [0]); ciff.Dump (); } */ protected override void Close () { if (stream != null) { stream.Close (); stream = null; } } } }