6
using System.Collections.Generic;
12
namespace FSpot.Tiff {
14
// This is primarily to preserve the names from the specification
15
// because they differ from the tiff standard names
16
public enum NiffId : ushort {
18
PelPathLength = 0x0100,
19
LineProgressionLength = 257,
20
BitsPerSample = 0x0101,
21
PhotometricInterpretation = 0x0106,
23
SamplesPerPixel = 0x0115,
24
DataByteCounts = 0x0117,
25
PelPathResolution = 0x011a,
26
LineProgressionResolution = 0x011b,
27
ResolutionUnit = 0x0128,
28
ColumnsPerPelPath = 322,
29
RowsPerLineProgression = 323,
31
NavyCompression = 33466,
35
public enum TagGPS : ushort {
36
GPSVersionID = 0x0000,
37
GPSLatitudeRef = 0x0001,
39
GPSLongitudeRef = 0x0003,
41
GPSAltitudeRef = 0x0005,
43
GPSTimeStamp = 0x0007,
44
GPSSatellites = 0x0008,
46
GPSMeasureMode = 0x000a,
52
GPSImgDirectionRef = 0x0010,
53
GPSImgDirection = 0x0011,
55
GPSDestLatitudeRef = 0x0013,
56
GPSDestLatitude = 0x0014,
57
GPSDestLongitudeRef = 0x0015,
58
GPSDestLongitude = 0x0016,
59
GPSDestBearingRef = 0x0017,
60
GPSDestBearing = 0x0018,
61
GPSDestDistanceRef = 0x0019,
62
GPSDestDistance = 0x001a,
63
GPSProcessingMethod = 0x001b,
64
GPSAreaInformation = 0x001c,
65
GPSDateStamp = 0x001d,
66
GPSDifferential = 0x001e
69
public enum TagId : ushort {
70
InteroperabilityIndex = 0x0001,
71
InteroperabilityVersion = 0x0002,
73
NewSubfileType = 0x00fe,
78
BitsPerSample = 0x0102,
80
PhotometricInterpretation = 0x0106,
82
DocumentName = 0x010d,
83
ImageDescription = 0x010e,
86
StripOffsets = 0x0111,
88
SamplesPerPixel = 0x0115,
89
RowsPerStrip = 0x0116,
90
StripByteCounts = 0x0117,
93
PlanarConfiguration = 0x011c,
98
ResolutionUnit = 0x0128,
99
TransferFunction = 0x012d,
104
PrimaryChromaticities = 0x013f,
106
HalftoneHints = 0x0141,
110
TileOffsets = 0x0144,
111
TileByteCounts = 0x0145,
113
SubIFDs = 0x014a, // TIFF-EP
117
NumberOfInks = 0x014e,
120
TargetPrinter = 0x0151,
121
ExtraSamples = 0x0152,
122
SampleFormat = 0x0153,
123
SMinSampleValue = 0x0154,
124
SMaxSampleValue = 0x0155,
126
TransferRange = 0x0156,
128
ClipPath = 0x0157, // TIFF PageMaker Technote #2.
130
JPEGTables = 0x015b, // TIFF-EP
133
JPEGInterchangeFormat = 0x0201,
134
JPEGInterchangeFormatLength = 0x0202,
135
JPEGRestartInterval = 0x0203,
136
JPEGLosslessPredictors = 0x0205,
137
JPEGPointTransforms = 0x0206,
138
JPEGQTables = 0x0207,
139
JPEGDCTables = 0x0208,
140
JPEGACTables = 0x0209,
142
YCbCrCoefficients = 0x0211,
143
YCbCrSubSampling = 0x0212,
144
YCbCrPositioning = 0x0213,
146
ReferenceBlackWhite = 0x0214,
147
RelatedImageFileFormat = 0x1000,
148
RelatedImageWidth = 0x1001,
149
RelatedImageLength = 0x1002,
150
CFARepeatPatternDim = 0x828d,
152
BatteryLevel = 0x828f,
154
ExposureTime = 0x829a,
157
// These are from the NIFF spec and only really valid when the header begins with IIN1
158
// see the NiffTag enum for the specifcation specific names
160
NavyCompression = 0x82ba,
166
PhotoshopPrivate = 0x8649,
168
ExifIfdPointer = 0x8769,
169
InterColorProfile = 0x8773,
170
ExposureProgram = 0x8822,
171
SpectralSensitivity = 0x8824,
172
GPSInfoIfdPointer = 0x8825,
173
ISOSpeedRatings = 0x8827,
175
ExifVersion = 0x9000,
176
DateTimeOriginal = 0x9003,
177
DateTimeDigitized = 0x9004,
178
ComponentsConfiguration = 0x9101,
179
CompressedBitsPerPixel = 0x9102,
180
ShutterSpeedValue = 0x9201,
181
ApertureValue = 0x9202,
182
BrightnessValue = 0x9203,
183
ExposureBiasValue = 0x9204,
184
MaxApertureValue = 0x9205,
185
SubjectDistance = 0x9206,
186
MeteringMode = 0x9207,
187
LightSource = 0x9208,
189
FocalLength = 0x920a,
191
FlashEnergy_TIFFEP = 0x920b,// TIFF-EP
192
SpacialFrequencyResponse = 0x920c,// TIFF-EP
193
Noise = 0x920d,// TIFF-EP
194
FocalPlaneXResolution_TIFFEP = 0x920e,// TIFF-EP
195
FocalPlaneYResolution_TIFFEP = 0x920f,// TIFF-EP
196
FocalPlaneResolutionUnit_TIFFEP = 0x9210,// TIFF-EP
197
ImageName = 0x9211,// TIFF-EP
198
SecurityClassification = 0x9212,// TIFF-EP
200
ImageHistory = 0x9213, // TIFF-EP null separated list
202
SubjectArea = 0x9214,
204
ExposureIndex_TIFFEP = 0x9215, // TIFF-EP
205
TIFFEPStandardID = 0x9216, // TIFF-EP
206
SensingMethod_TIFFEP = 0x9217, // TIFF-EP
209
UserComment = 0x9286,
211
SubSecTimeOriginal = 0x9291,
212
SubSecTimeDigitized = 0x9292,
213
FlashPixVersion = 0xa000,
215
PixelXDimension = 0xa002,
216
PixelYDimension = 0xa003,
217
RelatedSoundFile = 0xa004,
218
InteroperabilityIfdPointer = 0xa005,
219
FlashEnergy = 0xa20b,
220
SpatialFrequencyResponse = 0xa20c,
221
FocalPlaneXResolution = 0xa20e,
222
FocalPlaneYResolution = 0xa20f,
223
FocalPlaneResolutionUnit = 0xa210,
224
SubjectLocation = 0xa214,
225
ExposureIndex = 0xa215,
226
SensingMethod = 0xa217,
229
ExifCFAPattern = 0xa302,
230
CustomRendered = 0xa401,
231
ExposureMode = 0xa402,
232
WhiteBalance = 0xa403,
233
DigitalZoomRatio = 0xa404,
234
FocalLengthIn35mmFilm = 0xa405,
235
SceneCaptureType = 0xa406,
236
GainControl = 0xa407,
240
DeviceSettingDescription = 0xa40b,
241
SubjectDistanceRange = 0xa40c,
242
ImageUniqueId = 0xa420,
244
// The Following IDs are not described the EXIF spec
247
// The XMP spec declares that XMP data should live 0x2bc when
248
// embedded in tiff images.
252
DNGVersion = 0xc612, // Ifd0
253
DNGBackwardVersion = 0xc613, // Ifd0
254
UniqueCameraModel = 0xc614, // Ifd0
255
LocalizedCameraModel = 0xc615, // Ifd0
256
CFAPlaneColor = 0xc616, // RawIfd
257
CFALayout = 0xc617, // RawIfd
258
LinearizationTable = 0xc618, // RawIfd
259
BlackLevelRepeatDim = 0xc619, // RawIfd
260
BlackLevel = 0xc61a, // RawIfd
261
BlackLevelDeltaH = 0xc61b, // RawIfd
262
BlackLevelDeltaV = 0xc61c, // RawIfd
263
WhiteLevel = 0xc61d, // RawIfd
264
DefaultScale = 0xc61e, // RawIfd
265
DefaultCropOrigin = 0xc61f, // RawIfd
266
DefaultCropSize = 0xc620, // RawIfd
267
ColorMatrix1 = 0xc621, // Ifd0
268
ColorMatrix2 = 0xc622, // Ifd0
269
CameraCalibration1 = 0xc623, // Ifd0
270
CameraCalibration2 = 0xc624, // Ifd0
271
ReductionMatrix1 = 0xc625, // Ifd0
272
ReductionMatrix2 = 0xc626, // Ifd0
273
AnalogBalance = 0xc627, // Ifd0
274
AsShotNetural = 0xc628, // Ifd0
275
AsShotWhiteXY = 0xc629, // Ifd0
276
BaselineExposure = 0xc62a, // Ifd0
277
BaselineNoise = 0xc62b, // Ifd0
278
BaselineSharpness = 0xc62c, // Ifd0
279
BayerGreeSpit = 0xc62d, // Ifd0
280
LinearResponseLimit = 0xc62e, // Ifd0
281
CameraSerialNumber = 0xc62f, // Ifd0
282
LensInfo = 0xc630, // Ifd0
283
ChromaBlurRadius = 0xc631, // RawIfd
284
AntiAliasStrength = 0xc632, // RawIfd
285
DNGPrivateData = 0xc634, // Ifd0
287
MakerNoteSafety = 0xc635, // Ifd0
289
// The Spec says BestQualityScale is 0xc635 but it appears to be wrong
290
//BestQualityScale = 0xc635, // RawIfd
291
BestQualityScale = 0xc63c, // RawIfd this looks like the correct value
293
CalibrationIlluminant1 = 0xc65a, // Ifd0
294
CalibrationIlluminant2 = 0xc65b, // Ifd0
296
// Print Image Matching data
297
PimIfdPointer = 0xc4a5
300
public struct SRational {
301
public int Numerator;
302
public int Denominator;
304
public SRational (byte [] raw_data, int offset, bool little)
306
Numerator = BitConverter.ToInt32 (raw_data, offset, little);
307
Denominator = BitConverter.ToInt32 (raw_data, offset, little);
310
public SRational (int numerator, int denominator)
312
Numerator = numerator;
313
Denominator = denominator;
316
public static SRational BitwiseCopy (Rational rational)
320
result.Numerator = unchecked ((int) rational.Numerator);
321
result.Denominator = unchecked ((int) rational.Denominator);
325
public override string ToString ()
327
if (Numerator == 0 || Denominator == 0)
328
return String.Format ("{0}/{1}", Numerator, Denominator);
329
else if (Numerator % Denominator == 0)
330
return String.Format ("{0}", Numerator / Denominator);
331
else if (Denominator % Numerator == 0)
332
return String.Format ("1/{0}", Denominator / Numerator);
334
return String.Format ("{0}/{1}", Numerator, Denominator);
337
public double Value {
339
return Numerator / (double)Denominator;
344
public struct Rational {
345
public uint Numerator;
346
public uint Denominator;
348
public Rational (uint numerator, uint denominator)
350
Numerator = numerator;
351
Denominator = denominator;
354
public Rational (string value)
356
string [] vals = value.Split ('/');
357
if (vals.Length == 2) {
358
this.Numerator = UInt32.Parse (vals [0]);
359
this.Denominator = UInt32.Parse (vals [1]);
361
} if (vals.Length == 1) {
362
double tmp = Double.Parse (value);
363
this.Numerator = (uint) (tmp * 100000);
364
this.Denominator = 100000;
366
throw new ParseException ("unable to parse rational value");
369
public override string ToString ()
371
if (Numerator == 0 || Denominator == 0)
372
return String.Format ("{0}/{1}", Numerator, Denominator);
373
else if (Numerator % Denominator == 0)
374
return String.Format ("{0}", Numerator / Denominator);
375
else if (Denominator % Numerator == 0)
376
return String.Format ("1/{0}", Denominator / Numerator);
378
return String.Format ("{0}/{1}", Numerator, Denominator);
381
public double Value {
383
return Numerator / (double)Denominator;
392
public UserComment (string value)
398
public UserComment (byte [] raw_data, bool little)
400
if (raw_data.Length == 8 || raw_data.Length == 0) {
402
Value = String.Empty;
404
} else if (raw_data.Length < 8) {
405
throw new Exception ("Invalid UserComment value, no charset found");
408
string charset = System.Text.Encoding.ASCII.GetString (raw_data, 0, 8);
409
System.Text.Encoding enc;
413
enc = System.Text.Encoding.ASCII;
417
enc = new System.Text.UnicodeEncoding (! little, true);
419
case "JIS\0\0\0\0\0":
420
// FIXME this requires mono locale extras
422
enc = System.Text.Encoding.GetEncoding ("euc-jp");
424
//System.Console.WriteLine ("missing jis0208 encoding");
425
enc = System.Text.Encoding.Default;
428
case "\0\0\0\0\0\0\0\0":
429
// FIXME the spec says to use the local encoding in this case, we could probably
430
// do something smarter, but whatever.
431
enc = System.Text.Encoding.Default;
435
throw new ParseException (System.String.Format ("Invalid charset name: {0}", charset));
439
// for (int i = 0; i < raw_data.Length; i++)
440
// System.Console.WriteLine ("{0} - \"{1}\"", raw_data [i].ToString ("x"), raw_data [i]);
441
Value = enc.GetString (raw_data, 8, raw_data.Length - 8).Trim('\0');
444
public byte [] GetBytes (bool is_little)
447
string description = Value;
448
System.Text.Encoding enc;
451
for (int i = 0; i < description.Length; i++) {
452
if (description [i] > 127) {
459
heading = "ASCII\0\0\0";
460
enc = new System.Text.ASCIIEncoding ();
462
heading = "Unicode\0";
463
enc = new System.Text.UnicodeEncoding (! is_little, true);
466
int len = enc.GetByteCount (description);
467
byte [] data = new byte [len + heading.Length];
468
System.Text.Encoding.ASCII.GetBytes (heading, 0, heading.Length, data, 0);
469
enc.GetBytes (Value, 0, Value.Length, data, heading.Length);
471
UserComment c = new UserComment (data, is_little);
472
//System.Console.WriteLine ("old = \"{0}\" new = \"{1}\" heading = \"{2}\"", c.Value, description, heading);
476
public override string ToString ()
478
return String.Format ("({0},charset={1})", Value, Charset);
482
public struct CFAPattern {
484
public ushort Columns;
485
public byte [] Values;
487
public CFAPattern (byte [] raw_data, bool little)
489
Columns = BitConverter.ToUInt16 (raw_data, 0, little);
490
Rows = BitConverter.ToUInt16 (raw_data, 2, little);
492
Values = new byte [Rows * Columns];
493
// FIXME the contents here may differ from what we
494
// expect, but for now we won't throw an exception
495
// since the entry still may be a valid structure.
496
if (raw_data.Length -4 < Values.Length)
497
System.Array.Copy (raw_data, 4, Values, 0, Values.Length);
501
* Note the Exif spec defines a CFA pattern tag that includes the row and column counts as shorts
502
* inside the first four bytes of the entry. The Tiff-EP standard define the CFARepeatPattern tag
503
* that contains the row and column counts presumably since the Exif version wouldn't allow you to
504
* alter the endian of the file without knowing the tag layout.
507
public CFAPattern (ushort rows, ushort cols, byte [] raw_data, bool little)
511
Values = new byte [rows * cols];
512
System.Array.Copy (raw_data, 0, Values, 0, Values.Length);
516
public struct OECFTable {
518
public ushort Columns;
519
public string [] Names;
520
public SRational [] Values;
522
public OECFTable (byte [] raw_data, bool little)
524
Columns = BitConverter.ToUInt16 (raw_data, 0, little);
525
Rows = BitConverter.ToUInt16 (raw_data, 2, little);
526
Names = new string [Columns];
527
Values = new SRational [Columns * Rows];
531
int type_size = DirectoryEntry.GetTypeSize (EntryType.SRational);
533
for (i = 0; i < Names.Length; i++)
534
Names [i] = ReadString (raw_data, ref pos);
536
for (i = 0; i < Values.Length; i++)
537
Values [i] = new SRational (raw_data, pos + i * type_size, little);
541
public string ReadString (byte [] data, ref int pos)
544
for (; pos < data.Length; pos++) {
548
return System.Text.Encoding.ASCII.GetString (data, start, pos - start);
552
public enum ExtraSamples {
558
public enum FileSource {
562
public enum PhotometricInterpretation : ushort {
567
TransparencyMask = 4,
568
Separated = 5, // CMYK
573
LogL = 32844, // Log Luminance
575
ColorFilterArray = 32803, // ColorFilterArray... the good stuff
576
LinearRaw = 34892 // DBG LinearRaw
579
public enum PlanarConfiguration {
584
public enum Compression {
591
JPEGStream = 7, // TIFF-EP stores full jpeg stream
596
NikonCompression = 34713,
597
Deflate_experimental = 0x80b2
600
public enum JPEGProc {
601
BaselineSequencial = 1,
602
LosslessHuffman = 14,
605
public enum SubfileType {
607
ReducedResolution = 2,
611
public enum ExposureProgram {
614
NormalProgram = 2, // Normal Program
615
AperturePriority = 3, // Aperture priority
616
ShutterPriorty = 4, // Shutter priority
617
CreativeProgram = 5, // Creative program
618
ActionProgram = 6, // Action program
619
PortraitMode = 7, // Portrait mode
620
LandscapeMode = 8 // Landscape mode
623
public enum ExposureMode {
629
public enum CustomRendered : ushort {
634
public enum SceneType {
635
DirectlyPhotographed = 1
638
public enum MeteringMode {
641
CenterWeightedAverage = 2,
648
public enum SceneCaptureType : ushort {
655
public enum GainControl : ushort {
663
public enum Contrast : ushort {
669
public enum Saturation : ushort {
675
public enum WhiteBalance : ushort {
680
public enum Sharpness : ushort {
686
public enum LightSource {
695
DaylightFluorescent = 12,
696
DaylightWhiteFluorescent = 13,
697
CoolWhiteFluorescent = 14,
698
WhiteFluorescent = 15,
706
ISOStudioTungsten = 24,
710
public enum ColorSpace {
711
StandardRGB = 1, // sRGB
713
Uncalibrated = 0xffff
716
public enum ComponentsConfiguration {
725
public enum ResolutionUnit : ushort {
731
public enum SensingMethod : short {
733
OneChipColorAreaSensor = 2,
734
TwoChipColorAreaSensor = 3,
735
ThreeChipColorAreaSensor = 4,
736
ColorSequentialAreaSensor = 5,
738
ColorSequentialLinearSensor = 8
742
public enum NewSubfileType : uint {
743
ReducedResolution = 1,
744
PageOfMultipage= 1 << 1,
745
TransparencyMask = 1 << 2
748
public enum EntryType {
761
Ifd // TIFF-EP - TIFF PageMaker TechnicalNote 2
766
public EntryType Type;
769
public string Description;
772
public class CanonTag : Tag {
773
// http://www.gvsoft.homedns.org/exif/makernote-canon.html
775
public enum CanonId {
777
CameraSettings1 = 0x0001,
779
CameraSettings2 = 0x0004,
781
FirmwareVersion = 0x0007,
782
ImageNumber = 0x0008,
785
CameraSerialNumber = 0x000c,
787
CustomFunctions = 0x000f
790
public CanonTag (CanonId id, EntryType type, int count, string name, string description)
792
this.Id = (ushort)id;
796
this.Description = description;
799
public static System.Collections.Hashtable Tags;
803
new CanonTag (CanonId.Unknown1, EntryType.Short, 6, null, null),
804
new CanonTag (CanonId.CameraSettings1, EntryType.Short, -1, "Camera Settings 1", "First Canon MakerNote settings section"),
805
new CanonTag (CanonId.Unknown2, EntryType.Short, 4, null, null),
806
new CanonTag (CanonId.CameraSettings2, EntryType.Short, -1, "Camera Settings 2", "Second Canon MakerNote settings section"),
807
new CanonTag (CanonId.ImageType, EntryType.Ascii, 32, "Image Type", null), // FIXME description
808
new CanonTag (CanonId.FirmwareVersion, EntryType.Ascii, 24, "Firmware Version", "Version of the firmware installed on the camera"),
809
new CanonTag (CanonId.ImageNumber, EntryType.Long, 1, "Image Number", null), // FIXME description
810
new CanonTag (CanonId.OwnerName, EntryType.Long, 32, "Owner Name", "Name of the Camera Owner"), // FIXME description
811
new CanonTag (CanonId.Unknown4, EntryType.Short, -1, null, null),
812
new CanonTag (CanonId.CameraSerialNumber, EntryType.Short, 1, "Serial Number", null), //FIXME description
813
new CanonTag (CanonId.Unknown4, EntryType.Short, -1, null, null),
814
new CanonTag (CanonId.CustomFunctions, EntryType.Short, -1, "Custom Functions", "Camera Custom Functions")
817
foreach (CanonTag tag in tags)
828
public class ParseException : System.Exception
830
public ParseException (string msg) : base (msg)
835
public class ShortReadException : ParseException
837
public ShortReadException () : base ("Short Read")
842
public class Converter {
843
public static uint ReadUInt (System.IO.Stream stream, Endian endian)
845
byte [] tmp = new byte [4];
847
if (stream.Read (tmp, 0, tmp.Length) < 4) {
849
System.Console.WriteLine ("short read XXXXXXXXXXXXXXXXXXXXXXx");
851
throw new ShortReadException ();
853
return BitConverter.ToUInt32 (tmp, 0, endian == Endian.Little);
856
public static ushort ReadUShort (System.IO.Stream stream, Endian endian)
858
byte [] tmp = new byte [2];
860
if (stream.Read (tmp, 0, tmp.Length) < 2) {
862
System.Console.WriteLine ("Short read");
864
throw new ShortReadException ();
867
return BitConverter.ToUInt16 (tmp, 0, endian == Endian.Little);
871
public class Header : SemWeb.StatementSource {
872
public Endian endian;
874
private uint directory_offset;
875
public ImageDirectory Directory;
877
// false seems a safe default
878
public bool Distinct {
879
get { return false; }
882
public Header (System.IO.Stream stream)
884
//using (new Timer ("new Tiff.Header")) {
885
byte [] data = new byte [8];
886
stream.Read (data, 0, data.Length);
887
if (data [0] == 'M' && data [1] == 'M')
889
else if (data [0] == 'I' && data [1] == 'I')
890
endian = Endian.Little;
892
ushort marker = BitConverter.ToUInt16 (data, 2, endian == Endian.Little);
895
//System.Console.WriteLine ("Found Standard Tiff Marker {0}", marker);
898
//System.Console.WriteLine ("Found Olympus Tiff Marker {0}", marker.ToString ("x"));
901
//System.Console.WriteLine ("Found Navy Interchange File Format Tiff Marker {0}", marker.ToString ("x"));
904
//System.Console.WriteLine ("Found Unknown Tiff Marker {0}", marker.ToString ("x"));
908
//System.Console.WriteLine ("Converting Something");
909
directory_offset = BitConverter.ToUInt32 (data, 4, endian == Endian.Little);
911
if (directory_offset < 8)
912
throw new ParseException ("Invalid IFD0 Offset [" + directory_offset.ToString () + "]");
915
System.Console.WriteLine ("Reading First IFD");
917
Directory = new ImageDirectory (stream, directory_offset, endian);
922
public void Select (SemWeb.StatementSink sink)
924
//using (new Timer ("Tiff.Header.Select")) {
925
SelectDirectory (Directory, sink);
929
public void SelectDirectory (ImageDirectory dir, StatementSink sink)
931
foreach (DirectoryEntry e in dir.Entries) {
933
System.Console.WriteLine ("{0}", e.Id);
937
System.IO.Stream iptcstream = new System.IO.MemoryStream (e.RawData);
938
FSpot.Iptc.IptcFile iptc = new FSpot.Iptc.IptcFile (iptcstream);
941
case TagId.PhotoshopPrivate:
942
System.IO.Stream bimstream = new System.IO.MemoryStream (e.RawData);
943
FSpot.Bim.BimFile bim = new FSpot.Bim.BimFile (bimstream);
947
System.IO.Stream xmpstream = new System.IO.MemoryStream (e.RawData);
948
FSpot.Xmp.XmpFile xmp = new FSpot.Xmp.XmpFile (xmpstream);
951
case TagId.ImageDescription:
952
MetadataStore.AddLiteral (sink, "dc:description", "rdf:Alt",
953
new SemWeb.Literal (e.ValueAsString [0], "x-default", null));
955
case TagId.UserComment:
956
MetadataStore.AddLiteral (sink, "exif:UserComment", "rdf:Alt",
957
new SemWeb.Literal (e.ValueAsString [0], "x-default", null));
959
case TagId.Copyright:
960
MetadataStore.AddLiteral (sink, "dc:rights", "rdf:Alt",
961
new SemWeb.Literal (e.ValueAsString [0], "x-default", null));
964
MetadataStore.Add (sink, "dc:creator", "rdf:Seq", e.ValueAsString);
966
case TagId.ExifIfdPointer:
968
ImageDirectory sub = ((SubdirectoryEntry)e).Directory [0];
969
SelectDirectory (sub, sink);
970
} catch (System.Exception exc) {
971
//System.Console.WriteLine (exc);
975
MetadataStore.AddLiteral (sink, "xmp:CreatorTool", e.ValueAsString [0]);
980
MetadataStore.AddLiteral (sink, "xmp:ModifyDate",
981
e.ValueAsDate.ToString ("yyyy-MM-ddThh:mm:ss"));
982
} catch (System.Exception ex) {
983
//System.Console.WriteLine (String.Format ("error parsing {0}{2}{1}", e.ValueAsString[0], ex, Environment.NewLine));
987
case TagId.DateTimeOriginal:
988
case TagId.DateTimeDigitized:
989
// FIXME subsectime needs to be included in these values
990
// FIXME shouldn't DateTimeOriginal be xmp:CreateDate? the spec says no but wtf?
992
MetadataStore.AddLiteral (sink, "exif:" + e.Id.ToString (),
993
e.ValueAsDate.ToString ("yyyy-MM-ddThh:mm:ss"));
994
} catch (System.Exception ex) {
995
//System.Console.WriteLine (String.Format ("error parsing {0}{2}{1}", e.ValueAsString[0], ex, Environment.NewLine));
998
//case TagId.SpatialFrequencyResponse
999
case TagId.ExifCFAPattern:
1000
CFAPattern pattern = new CFAPattern (e.RawData, e.IsLittle);
1001
Entity empty = new BNode ();
1002
Statement top = new Statement (MetadataStore.FSpotXMPBase,
1003
(Entity)MetadataStore.Namespaces.Resolve ("exif:" + e.Id.ToString ()),
1006
Statement cols = new Statement (empty,
1007
(Entity) MetadataStore.Namespaces.Resolve ("exif:Columns"),
1008
new SemWeb.Literal (pattern.Columns.ToString (), null, null));
1010
Statement rows = new Statement (empty,
1011
(Entity) MetadataStore.Namespaces.Resolve ("exif:Rows"),
1012
new SemWeb.Literal (pattern.Rows.ToString (), null, null));
1014
string [] vals = e.ArrayToString (pattern.Values);
1015
MetadataStore.Add (sink, empty, "exif:Values", "rdf:Seq", vals);
1018
case TagId.ExifVersion:
1019
case TagId.FlashPixVersion:
1020
case TagId.ColorSpace:
1021
case TagId.CompressedBitsPerPixel:
1022
case TagId.PixelYDimension:
1023
case TagId.PixelXDimension:
1024
case TagId.RelatedSoundFile:
1025
case TagId.ExposureTime:
1027
case TagId.ExposureProgram:
1028
case TagId.SpectralSensitivity:
1029
case TagId.ShutterSpeedValue:
1030
case TagId.ApertureValue:
1031
case TagId.BrightnessValue:
1032
case TagId.ExposureBiasValue:
1033
case TagId.MaxApertureValue:
1034
case TagId.SubjectDistance:
1035
case TagId.MeteringMode:
1036
case TagId.LightSource:
1037
case TagId.FocalLength:
1038
case TagId.FlashEnergy:
1039
case TagId.FocalPlaneXResolution:
1040
case TagId.FocalPlaneYResolution:
1041
case TagId.FocalPlaneResolutionUnit:
1042
case TagId.ExposureIndex:
1043
case TagId.SensingMethod:
1044
case TagId.FileSource:
1045
case TagId.SceneType:
1046
case TagId.CustomRendered:
1047
case TagId.ExposureMode:
1048
case TagId.WhiteBalance:
1049
case TagId.DigitalZoomRatio:
1050
case TagId.FocalLengthIn35mmFilm:
1051
case TagId.SceneCaptureType:
1052
case TagId.GainControl:
1053
case TagId.Contrast:
1054
case TagId.Saturation:
1055
case TagId.Sharpness:
1056
MetadataStore.AddLiteral (sink, "exif:" + e.Id.ToString (), e.ValueAsString [0]);
1058
case TagId.ComponentsConfiguration:
1059
case TagId.ISOSpeedRatings:
1060
case TagId.SubjectArea:
1061
case TagId.SubjectLocation:
1062
MetadataStore.Add (sink, "exif:" + e.Id.ToString (), "rdf:Seq", e.ValueAsString);
1064
case TagId.TransferFunction:
1065
case TagId.YCbCrSubSampling:
1066
case TagId.WhitePoint:
1067
case TagId.PrimaryChromaticities:
1068
case TagId.YCbCrCoefficients:
1069
case TagId.ReferenceBlackWhite:
1070
case TagId.BitsPerSample:
1071
MetadataStore.Add (sink, "tiff:" + e.Id.ToString (), "rdf:Seq", e.ValueAsString);
1073
case TagId.Orientation:
1074
case TagId.Compression:
1075
case TagId.PhotometricInterpretation:
1076
case TagId.SamplesPerPixel:
1077
case TagId.PlanarConfiguration:
1078
case TagId.YCbCrPositioning:
1079
case TagId.ResolutionUnit:
1080
case TagId.ImageWidth:
1081
case TagId.ImageLength:
1085
MetadataStore.AddLiteral (sink, "tiff:" + e.Id.ToString (), e.ValueAsString [0]);
1086
} catch (System.Exception ex) {
1087
//System.Console.WriteLine (String.Format ("error parsing {0}{2}{1}", e.Id, ex, Environment.NewLine));
1094
public void Save (System.IO.Stream out_stream)
1096
OrderedWriter writer = new OrderedWriter (out_stream, endian == Endian.Little);
1099
if (endian == Endian.Little) {
1100
writer.Write ((byte)'I');
1101
writer.Write ((byte)'I');
1103
writer.Write ((byte)'M');
1104
writer.Write ((byte)'M');
1107
writer.Write ((ushort)42);
1110
Directory.Save (writer, 8);
1113
public void Dump (string name)
1115
ImageDirectory ifd = Directory;
1116
for (int i = 0; ifd != null; i++) {
1117
ifd.Dump (System.String.Format ("IFD[{0}]:", i));
1118
ifd = ifd.NextDirectory;
1123
public class ImageDirectory {
1124
protected Endian endian;
1125
protected ushort num_entries;
1126
protected List<DirectoryEntry> entries;
1127
protected uint orig_position;
1129
protected uint next_directory_offset;
1130
ImageDirectory next_directory;
1132
protected bool has_header;
1133
protected bool has_footer;
1135
public ImageDirectory (System.IO.Stream stream, uint start_position, Endian endian)
1137
this.endian = endian;
1138
orig_position = start_position;
1142
public uint Save (OrderedWriter writer, uint position)
1144
writer.Write (position);
1145
writer.Stream.Position = position;
1147
writer.Write ((ushort)entries.Count);
1150
uint value_position = (uint) (position + 12 * entries.Count + 4);
1152
for (int i = 0; i < entries.Count; i++) {
1153
writer.Stream.Position = position + (12 * i);
1154
value_position = (uint)Entries[i].Save (writer, value_position);
1157
writer.Stream.Position = position + (12 * entries.Count);
1158
if (next_directory != null)
1159
value_position = next_directory.Save (writer, value_position);
1161
writer.Write ((uint) 0);
1163
return value_position;
1166
protected void Load (System.IO.Stream stream)
1168
ReadHeader (stream);
1169
ReadEntries (stream);
1170
ReadFooter (stream);
1172
LoadEntries (stream);
1173
LoadNextDirectory (stream);
1176
public virtual bool ReadHeader (System.IO.Stream stream)
1178
stream.Seek ((long)orig_position, System.IO.SeekOrigin.Begin);
1182
protected virtual void ReadEntries (System.IO.Stream stream)
1184
num_entries = Converter.ReadUShort (stream, endian);
1186
System.Console.WriteLine ("reading {0} entries", num_entries);
1188
entries = new List<DirectoryEntry> (num_entries);
1189
int entry_length = num_entries * 12;
1190
byte [] content = new byte [entry_length];
1192
if (stream.Read (content, 0, content.Length) < content.Length) {
1194
System.Console.WriteLine ("short read XXXXXXXXXXXXXXXXXXXXXXx");
1196
throw new ShortReadException ();
1200
for (int pos = 0; pos < entry_length; pos += 12) {
1201
DirectoryEntry entry = CreateEntry (this, content, pos, this.endian);
1202
entries.Add (entry);
1204
System.Console.WriteLine ("Added Entry {0} {1} - {2} * {3}", entry.Id.ToString (), entry.Id.ToString ("x"), entry.Type, entry.Count);
1206
if (entry.Id == TagId.NewSubfileType) {
1212
protected virtual void ReadFooter (System.IO.Stream stream)
1214
next_directory_offset = Converter.ReadUInt (stream, this.endian);
1217
protected void LoadEntries (System.IO.Stream stream)
1219
foreach (DirectoryEntry entry in entries) {
1220
entry.LoadExternal (stream);
1224
protected void LoadNextDirectory (System.IO.Stream stream)
1227
System.Console.WriteLine ("start_position = {1} next_directory_offset = {0}",
1228
next_directory_offset, orig_position);
1230
next_directory = null;
1232
if (next_directory_offset != 0 && next_directory_offset != orig_position)
1233
next_directory = new ImageDirectory (stream, next_directory_offset, this.endian);
1235
} catch (System.Exception) {
1236
//System.Console.WriteLine ("Error loading directory {0}", e.ToString ());
1237
next_directory = null;
1238
next_directory_offset = 0;
1242
public ImageDirectory NextDirectory {
1244
return next_directory;
1248
public List<DirectoryEntry> Entries {
1254
public DirectoryEntry Lookup (TagId id)
1256
foreach (DirectoryEntry entry in entries)
1264
private DirectoryEntry GetEntry (int i)
1266
if (i < Entries.Count)
1272
public DirectoryEntry Lookup (uint id)
1274
foreach (DirectoryEntry entry in entries)
1275
if ((uint)entry.Id == id)
1281
public static DirectoryEntry CreateEntry (ImageDirectory parent, byte [] input, int start, Endian header_endian)
1286
DirectoryEntry.ParseHeader (input, start, out tagid, out type, header_endian);
1287
//ConstructorFunc ctor = ctors[tagid];
1288
//if (ctor == null) {
1289
// return ctor (input, header_endian);
1293
case TagId.ExifIfdPointer:
1294
case TagId.GPSInfoIfdPointer:
1295
case TagId.InteroperabilityIfdPointer:
1297
return new SubdirectoryEntry (input, start, header_endian);
1298
//case TagId.MakerNote:
1299
//return new MakerNoteEntry (input, start, header_endian);
1300
//case TagId.PimIfdPointer:
1302
//case TagId.MakerNote:
1303
//return new MakerNoteEntry (input, start, header_endian);
1308
//System.Console.WriteLine ("Trying to load {0} {1}", tagid, tagid.ToString ("x"));
1309
return new SubdirectoryEntry (input, start, header_endian);
1310
case EntryType.Byte:
1311
return new ByteEntry (input, start, header_endian);
1312
case EntryType.Long:
1313
return new LongEntry (input, start, header_endian);
1314
case EntryType.Short:
1315
return new ShortEntry (input, start, header_endian);
1318
return new DirectoryEntry (input, start, header_endian);
1322
public Cms.Profile GetProfile ()
1324
Cms.ColorCIExyY whitepoint = new Cms.ColorCIExyY (0, 0, 0);
1325
Cms.ColorCIExyYTriple primaries = new Cms.ColorCIExyYTriple (whitepoint, whitepoint, whitepoint);
1326
Cms.GammaTable [] transfer = null;
1327
int bits_per_sample = 8;
1330
foreach (DirectoryEntry e in entries) {
1332
case TagId.InterColorProfile:
1334
return new Cms.Profile (e.RawData);
1335
} catch (System.Exception ex) {
1336
//System.Console.WriteLine (ex);
1339
case TagId.ColorSpace:
1340
switch ((ColorSpace)e.ValueAsLong [0]) {
1341
case ColorSpace.StandardRGB:
1342
return Cms.Profile.CreateStandardRgb ();
1343
case ColorSpace.AdobeRGB:
1344
return Cms.Profile.CreateAlternateRgb ();
1345
case ColorSpace.Uncalibrated:
1346
//System.Console.WriteLine ("Uncalibrated colorspace");
1350
case TagId.WhitePoint:
1351
Rational [] white = e.RationalValue;
1352
whitepoint.x = white [0].Value;
1353
whitepoint.y = white [1].Value;
1356
case TagId.PrimaryChromaticities:
1357
Rational [] colors = e.RationalValue;
1358
primaries.Red.x = colors [0].Value;
1359
primaries.Red.y = colors [1].Value;
1360
primaries.Red.Y = 1.0;
1362
primaries.Green.x = colors [2].Value;
1363
primaries.Green.y = colors [3].Value;
1364
primaries.Green.Y = 1.0;
1366
primaries.Blue.x = colors [4].Value;
1367
primaries.Blue.y = colors [5].Value;
1368
primaries.Blue.Y = 1.0;
1370
case TagId.TransferFunction:
1371
ushort [] trns = e.ShortValue;
1372
ushort gamma_count = (ushort) (1 << bits_per_sample);
1373
Cms.GammaTable [] tables = new Cms.GammaTable [3];
1374
//System.Console.WriteLine ("Parsing transfer function: count = {0}", trns.Length);
1376
// FIXME we should use the TransferRange here
1377
// FIXME we should use bits per sample here
1378
for (int c = 0; c < 3; c++) {
1379
tables [c] = new Cms.GammaTable (trns, c * gamma_count, gamma_count);
1384
case TagId.ExifIfdPointer:
1385
SubdirectoryEntry exif = (SubdirectoryEntry) e;
1386
DirectoryEntry ee = exif.Directory [0].Lookup ((int)TagId.Gamma);
1391
Rational rgamma = ee.RationalValue [0];
1392
gamma = rgamma.Value;
1397
if (transfer == null) {
1398
Cms.GammaTable basic = new Cms.GammaTable (1 << bits_per_sample, gamma);
1399
transfer = new Cms.GammaTable [] { basic, basic, basic };
1402
// if we didn't get a white point or primaries, give up
1403
if (whitepoint.Y != 1.0 || primaries.Red.Y != 1.0)
1406
return new Cms.Profile (whitepoint, primaries, transfer);
1410
public void Dump (string name)
1412
System.Console.WriteLine ("Starting {0}", name);
1413
foreach (DirectoryEntry e in this.Entries)
1415
System.Console.WriteLine ("Ending {0}", name);
1418
public string Dump2 ()
1420
System.Text.StringBuilder builder = new System.Text.StringBuilder ();
1421
builder.Append ("Dummping IFD");
1422
foreach (DirectoryEntry entry in entries) {
1423
builder.Append (entry.ToString ()+ Environment.NewLine);
1425
if (entry is SubdirectoryEntry)
1426
builder.Append ("Found SUBDIRECTORYENTRY" + Environment.NewLine);
1429
if (next_directory != null) {
1430
builder.Append ("Dummping Next IFD");
1431
builder.Append (next_directory.Dump2 ());
1434
return builder.ToString ();
1438
public class MakerNoteEntry : SubdirectoryEntry {
1439
public MakerNoteEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
1444
public override uint GetEntryCount ()
1449
public override void LoadExternal (System.IO.Stream stream)
1455
public class SubdirectoryEntry : DirectoryEntry {
1456
public uint directory_offset;
1457
public ImageDirectory [] Directory;
1459
public SubdirectoryEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
1461
if (this.GetEntryCount () > 1) {
1462
//System.Console.WriteLine ("Count is greater than 1 ({1}) on Subdirectory {0} interesting", tagid, count);
1466
public override uint Save (OrderedWriter writer, uint position)
1469
Console.WriteLine ("writing entry {0} {1} {2} - value offset = {3}", Id, Type, Count, position);
1472
writer.Write ((ushort)Id);
1473
writer.Write ((ushort)Type);
1474
writer.Write ((uint)Count);
1477
if (Directory.Length == 1) {
1478
writer.Write ((uint)position);
1479
position = Directory [0].Save (writer, position);
1480
} else if (Directory.Length > 1) {
1481
writer.Write ((uint)position);
1482
uint value_position = (uint) (position + Directory.Length * 4);
1483
for (int i = 0; i < Directory.Length; i++) {
1484
writer.Stream.Position = position + i * 4;
1485
value_position = Directory [i].Save (writer, value_position);
1487
return value_position;
1489
writer.Write ((uint) 0);
1494
public virtual uint GetEntryCount ()
1499
public override void LoadExternal (System.IO.Stream stream)
1501
uint entry_count = GetEntryCount ();
1502
Directory = new ImageDirectory [entry_count];
1504
base.LoadExternal (stream);
1506
for (int i = 0; i < entry_count; i++) {
1508
directory_offset = BitConverter.ToUInt32 (raw_data, i * 4, endian == Endian.Little);
1509
if (directory_offset == offset_origin + 8)
1510
throw new Exception ("recursive ifd");
1511
Directory [i] = new ImageDirectory (stream, directory_offset, endian);
1512
} catch (System.Exception e) {
1513
//System.Console.WriteLine ("Error loading Subdirectory {0} at {2} of {3}bytes:{4}{1}",
1514
// this.Id, e, directory_offset, stream.Length, Environment.NewLine);
1520
public override void Dump (string name)
1522
for (int i = 0; i < GetEntryCount (); i++) {
1523
string subdirname = System.String.Format ("{0}{1}[{2}]({3})]", name, tagid, i, directory_offset);
1526
if (Directory [i] != null)
1527
Directory [i].Dump (subdirname);
1528
} catch (System.Exception e) {
1529
System.Console.WriteLine (e);
1535
public class ShortEntry : DirectoryEntry {
1536
public ShortEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
1541
public class LongEntry : DirectoryEntry {
1542
public LongEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
1544
if (type != EntryType.Long)
1545
throw new ParseException (System.String.Format ("Invalid Settings At Birth {0}", tagid));
1549
public class ByteEntry : DirectoryEntry {
1550
public ByteEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
1552
if (type != EntryType.Byte)
1553
throw new ParseException ("Invalid Settings At Birth");
1558
public class ImageLoader {
1562
PhotometricInterpretation interpretation;
1563
Compression compression;
1565
uint [] strip_byte_counts;
1566
uint rows_per_strip;
1569
public ImageLoader (ImageDirectory directory)
1571
width = directory.Lookup (TagId.ImageWidth).ValueAsLong [0];
1572
length = directory.Lookup (TagId.ImageLength).ValueAsLong [0];
1573
bps = directory.Lookup (TagId.BitsPerSample).ValueAsLong;
1575
compression = (Compression) directory.Lookup (TagId.Compression).ValueAsLong [0];
1576
interpretation = (PhotometricInterpretation) directory.Lookup (TagId.PhotometricInterpretation).ValueAsLong [0];
1578
offsets = directory.Lookup (TagId.StripOffsets).ValueAsLong;
1579
strip_byte_counts = directory.Lookup (TagId.StripByteCounts).ValueAsLong;
1580
rows_per_strip = directory.Lookup (TagId.RowsPerStrip).ValueAsLong [0];
1582
if (interpretation !=
1587
public Gdk.Pixbuf LoadPixbuf (System.IO.Stream stream)
1589
Gdk.Pixbuf dest = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, width, height);
1590
strip = new byte [strip_byte_counts];
1592
for (int i = 0; i < offsets.Length; i++) {
1593
strip = new byte [strip_byte_counts [i]];
1594
stream.Read (strip, 0, strip.Length);
1595
switch (compression) {
1596
case Compression.Notice
1605
public class DirectoryEntry {
1606
protected TagId tagid;
1607
protected EntryType type;
1608
protected uint count;
1609
protected uint offset_origin;
1610
protected uint data_offset;
1612
protected byte [] raw_data;
1613
protected Endian endian;
1621
public EntryType Type {
1633
public virtual uint Save (OrderedWriter writer, uint position)
1636
Console.WriteLine ("writing entry {0} {1} {2}", Id, Type, Count);
1638
writer.Write ((ushort)Id);
1639
writer.Write ((ushort)Type);
1640
writer.Write ((uint)Count);
1642
writer.Write ((uint)position);
1643
writer.Stream.Position = position;
1644
writer.Stream.Write (RawData, 0, RawData.Length);
1645
return (uint) (position + RawData.Length);
1647
writer.Stream.Write (RawData, 0, RawData.Length);
1648
for (int i = 0; i < 4 - RawData.Length; i++)
1649
writer.Write ((byte)0);
1654
public void SetOrigin (uint pos)
1656
offset_origin = pos;
1659
public uint Position {
1661
return offset_origin + data_offset;
1665
public uint Length {
1666
get { return (uint)(Count * GetTypeSize ()); }
1669
public virtual int GetTypeSize ()
1671
return GetTypeSize (type);
1674
public static int GetTypeSize (EntryType type)
1677
case EntryType.Byte:
1678
case EntryType.SByte:
1679
case EntryType.Undefined:
1680
case EntryType.Ascii:
1682
case EntryType.Short:
1683
case EntryType.SShort:
1685
case EntryType.Long:
1686
case EntryType.SLong:
1687
case EntryType.Float:
1689
case EntryType.Double:
1690
case EntryType.Rational:
1691
case EntryType.SRational:
1698
public bool IsLittle {
1700
return (endian == Endian.Little);
1704
public static int ParseHeader (byte [] data, int start, out TagId tagid, out EntryType type, Endian endian)
1706
tagid = (TagId) BitConverter.ToUInt16 (data, start, endian == Endian.Little);
1707
type = (EntryType) BitConverter.ToUInt16 (data, start + 2, endian == Endian.Little);
1711
public DirectoryEntry (byte [] data, int start, Endian endian)
1713
this.endian = endian;
1715
start += ParseHeader (data, start, out this.tagid, out this.type, endian);
1716
ParseStream (data, start);
1719
public virtual void LoadExternal (System.IO.Stream stream)
1721
if (data_offset != 0) {
1722
stream.Seek ((long)Position, System.IO.SeekOrigin.Begin);
1723
byte [] data = new byte [count * GetTypeSize ()];
1724
if (stream.Read (data, 0, data.Length) < data.Length) {
1726
System.Console.WriteLine ("Short read");
1728
throw new ShortReadException ();
1734
switch ((int)this.Id) {
1735
case (int)TagId.NewSubfileType:
1736
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new NewSubFileType {0}", (NewSubfileType) this.ValueAsLong [0]);
1738
case (int)TagId.SubfileType:
1739
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new SubFileType {0}", (SubfileType) this.ValueAsLong [0]);
1741
case (int)TagId.Compression:
1742
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new Compression {0}", (Compression) this.ValueAsLong [0]);
1745
case (int)TagId.JPEGProc:
1746
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new JPEGProc {0}", (JPEGProc) this.ValueAsLong [0]);
1749
case (int)TagId.PhotometricInterpretation:
1750
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new PhotometricInterpretation {0}", (PhotometricInterpretation) this.ValueAsLong [0]);
1752
case (int)TagId.ImageWidth:
1753
case (int)TagId.ImageLength:
1754
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new {1} {0}", this.ValueAsLong [0], this.Id);
1759
//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX {0}({1}) - {2} {3}", this.Id, this.Id.ToString ("x"), this.type, raw_data.Length);
1760
//System.Console.WriteLine ("XXXX ", System.Text.Encoding.ASCII.GetString (raw_data));
1761
switch (this.type) {
1762
case EntryType.Long:
1763
foreach (uint val in ((LongEntry)this).Value)
1764
System.Console.Write (" {0}", val);
1766
case EntryType.Short:
1767
foreach (ushort val in ((ShortEntry)this).ShortValue)
1768
System.Console.Write (" {0}", val);
1770
case EntryType.Byte:
1771
foreach (byte val in this.RawData)
1772
System.Console.Write (" {0}", val);
1775
//System.Console.WriteLine (String.Empty);
1781
public virtual void Dump (string name)
1783
switch (this.Type) {
1784
case EntryType.Short:
1785
case EntryType.Long:
1786
uint [] vals = this.ValueAsLong;
1787
System.Console.Write ("{3}{1}({2}) [{0}] (", vals.Length, this.Id, this.Type, name);
1788
for (int i = 0; i < System.Math.Min (15, vals.Length); i++) {
1789
System.Console.Write (" {0}", vals [i]);
1791
System.Console.WriteLine (")");
1793
case EntryType.Ascii:
1794
System.Console.WriteLine ("{3}{1}({2}) (\"{0}\")", this.StringValue, this.Id, this.Type, name);
1797
System.Console.WriteLine ("{3}{1}({2}) [{0}]", this.Count, this.Id, this.Type, name);
1802
protected void ParseStream (byte [] data, int start)
1806
count = BitConverter.ToUInt32 (data, i, endian == Endian.Little);
1808
int size = (int)count * GetTypeSize ();
1810
data_offset = BitConverter.ToUInt32 (data, i, endian == Endian.Little);
1813
raw_data = new byte [size];
1814
System.Array.Copy (data, i, raw_data, 0, size);
1818
public void SetData (string value)
1820
int len = System.Text.Encoding.UTF8.GetByteCount (value);
1821
byte [] tmp = new byte [len + 1];
1822
System.Text.Encoding.UTF8.GetBytes (value, 0, value.Length, tmp, 0);
1824
//System.Console.WriteLine ("SetData: value = {0} len = {1}", value, len);
1828
public static System.DateTime DateTimeFromString (string dt)
1830
// Exif DateTime strings are formatted as
1831
// "YYYY:MM:DD HH:MM:SS"
1833
string delimiters = " :";
1834
string[] dt_data = dt.Split ( delimiters.ToCharArray(), 6 );
1835
System.DateTime result;
1836
result = new System.DateTime (System.Int32.Parse(dt_data[0]),
1837
System.Int32.Parse(dt_data[1]),
1838
System.Int32.Parse(dt_data[2]),
1839
System.Int32.Parse(dt_data[3]),
1840
System.Int32.Parse(dt_data[4]),
1841
System.Int32.Parse(dt_data[5]));
1846
public void SetData (byte [] data)
1849
count = (uint)raw_data.Length / (uint)GetTypeSize ();
1853
public object GetValue () {
1855
case EntryType.Short:
1857
case EntryType.Long:
1859
case EntryType.Rational:
1860
return RationalValue;
1861
case EntryType.SRational:
1862
return SRationalValue;
1863
case EntryType.Ascii:
1864
return StringValue.Split ('\0');
1867
//System.Console.WriteLine ("{1}({2}) [{0}]", this.Count, this.Id, this.Type);
1875
public byte [] Value {
1881
public byte [] RawData {
1887
public string [] ValueAsString {
1889
switch (this.Type) {
1890
case EntryType.Short:
1891
case EntryType.Long:
1892
return ArrayToString (this.ValueAsLong);
1893
case EntryType.Rational:
1894
return ArrayToString (this.RationalValue);
1895
case EntryType.SRational:
1896
return ArrayToString (this.SRationalValue);
1897
case EntryType.Undefined:
1899
case TagId.UserComment:
1900
return new string [] { UserCommentValue };
1901
case TagId.FlashPixVersion:
1902
case TagId.ExifVersion:
1903
return new string [] { StringValue };
1904
case TagId.FileSource:
1905
case TagId.SceneType:
1906
return ArrayToString (this.RawData);
1907
case TagId.ComponentsConfiguration:
1908
return ArrayToString (ValueAsLong);
1910
//System.Console.WriteLine ("Cannot convert type \"{0}\" to string", Id);
1914
case EntryType.Ascii:
1915
return StringValue.Split ('\0');
1921
public string [] ArrayToString (System.Array array)
1923
string [] vals = new string [array.Length];
1924
for (int i = 0; i < array.Length; i++)
1925
vals [i] = array.GetValue (i).ToString ();
1930
public uint [] ValueAsLong {
1932
uint [] data = new uint [this.Count];
1933
for (int i = 0; i < this.Count; i++) {
1934
switch (this.Type) {
1935
case EntryType.Long:
1936
data [i] = BitConverter.ToUInt32 (raw_data, i * GetTypeSize (), endian == Endian.Little);
1938
case EntryType.Short:
1939
data [i] = BitConverter.ToUInt16 (raw_data, i * GetTypeSize (), endian == Endian.Little);
1941
case EntryType.Undefined:
1942
case EntryType.Byte:
1943
data [i] = raw_data [i];
1946
throw new ParseException ("Invalid conversion");
1953
// The following methods are usded to convert the data
1954
// to the various type regardless of the entry
1955
// type, they are used internally in processing the data
1956
// Use at your own risk.
1958
public string StringValue {
1960
return System.Text.Encoding.ASCII.GetString (raw_data);
1964
public System.DateTime ValueAsDate {
1966
return DirectoryEntry.DateTimeFromString (StringValue);
1970
public string UserCommentValue {
1972
UserComment comment = new UserComment (raw_data, IsLittle);
1973
return comment.Value;
1977
public SRational [] SRationalValue {
1979
Rational [] vals = RationalValue;
1980
SRational [] data = new SRational [vals.Length];
1982
for (int i = 0; i < vals.Length; i++)
1983
data [i] = SRational.BitwiseCopy (vals [i]);
1989
public Rational [] RationalValue {
1991
uint [] vals = LongValue;
1992
Rational [] data = new Rational [vals.Length / 2];
1994
for (int i = 0; i < vals.Length; i += 2)
1995
data [i/2] = new Rational (vals [i], vals [i + 1]);
2002
public uint [] LongValue {
2004
uint [] data = new uint [raw_data.Length / 4];
2005
for (int i = 0; i < raw_data.Length; i+= 4)
2006
data [i/4] = BitConverter.ToUInt32 (raw_data, i, endian == Endian.Little);
2012
public ushort [] ShortValue {
2014
ushort [] data = new ushort [raw_data.Length];
2015
for (int i = 0; i < raw_data.Length; i+= 2) {
2016
data [i] = BitConverter.ToUInt16 (raw_data, i, endian == Endian.Little);
2024
public class TiffFile : ImageFile, SemWeb.StatementSource {
2025
public Header Header;
2027
// false seems a safe default
2028
public bool Distinct {
2029
get { return false; }
2032
public TiffFile (string path) : base (path)
2035
using (System.IO.Stream input = Open ()) {
2036
this.Header = new Header (input);
2040
Header.Dump (this.ToString () + ":");
2042
} catch (System.Exception e) {
2043
Beagle.Util.Log.Error (e, "Error loading TIFF file {0}", path);
2047
public TiffFile (Uri uri) : base (uri)
2050
using (System.IO.Stream input = Open ()) {
2051
this.Header = new Header (input);
2055
Header.Dump (this.ToString () + ":");
2057
} catch (System.Exception e) {
2058
Beagle.Util.Log.Error (e, "Error loading TIFF file {0}", uri);
2062
public virtual void Select (SemWeb.StatementSink sink)
2064
Header.SelectDirectory (Header.Directory, sink);
2067
public override System.DateTime Date {
2069
SubdirectoryEntry sub = (SubdirectoryEntry) this.Header.Directory.Lookup (TagId.ExifIfdPointer);
2073
e = sub.Directory [0].Lookup (TagId.DateTimeOriginal);
2076
return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
2079
e = this.Header.Directory.Lookup (TagId.DateTime);
2082
return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
2088
public override System.IO.Stream PixbufStream ()
2093
public override PixbufOrientation GetOrientation ()
2095
ShortEntry e = (ShortEntry)(this.Header.Directory.Lookup (TagId.Orientation));
2097
return (PixbufOrientation)(e.ShortValue[0]);
2099
return PixbufOrientation.TopLeft;
2102
public System.IO.Stream LookupJpegSubstream (ImageDirectory directory)
2104
uint offset = directory.Lookup (TagId.JPEGInterchangeFormat).ValueAsLong [0];
2106
System.IO.Stream file = Open ();
2107
file.Position = offset;
2112
public Gdk.Pixbuf LoadJpegInterchangeFormat (ImageDirectory directory)
2114
uint offset = directory.Lookup (TagId.JPEGInterchangeFormat).ValueAsLong [0];
2115
uint length = directory.Lookup (TagId.JPEGInterchangeFormatLength).ValueAsLong [0];
2117
using (System.IO.Stream file = Open ()) {
2118
file.Position = offset;
2120
byte [] data = new byte [32768];
2123
Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
2125
while (length > 0) {
2126
read = file.Read (data, 0, (int)System.Math.Min ((int)data.Length, length));
2130
loader.Write (data, (ulong)read);
2131
length -= (uint) read;
2133
Gdk.Pixbuf result = loader.Pixbuf;
2141
public class DngFile : TiffFile {
2142
public DngFile (string path) : base (path)
2146
public DngFile (System.Uri uri) : base (uri)
2150
public override System.IO.Stream PixbufStream ()
2153
SubdirectoryEntry sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);
2154
ImageDirectory directory = sub.Directory [sub.Directory.Length - 1];
2156
uint offset = directory.Lookup (TagId.StripOffsets).ValueAsLong [0];
2157
System.IO.Stream file = Open ();
2158
file.Position = offset;
2161
return DCRawFile.RawPixbufStream (uri);
2165
public override void Select (SemWeb.StatementSink sink)
2168
/* this is just a sanity pass, if the first ifd is not a subfile use the normal
2171
DirectoryEntry e = Header.Directory.Lookup (TagId.NewSubfileType);
2178
* Even though Ifd0 doesn't have the full resolution image
2179
* it would have the XMP data so we look for it
2181
e = Header.Directory.Lookup (TagId.XMP);
2183
System.IO.Stream xmpstream = new System.IO.MemoryStream (e.RawData);
2184
FSpot.Xmp.XmpFile xmp = new FSpot.Xmp.XmpFile (xmpstream);
2189
* Ifd0 will also have the exif directory
2191
ImageDirectory dir = Header.Directory;
2192
SubdirectoryEntry sub = (SubdirectoryEntry) dir.Lookup (TagId.ExifIfdPointer);
2194
Header.SelectDirectory (sub.Directory [0], sink);
2197
* now we lookup subifd0 (we should probably scan the newsubfile types here)
2198
* and load the metadata we are interested in from it.
2200
sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);
2203
e = dir.Lookup (TagId.NewSubfileType);
2207
uint dirtype = e.ValueAsLong [0];
2209
Header.SelectDirectory (dir, sink);
2216
dir = sub.Directory [i];
2217
e = dir.Lookup (TagId.NewSubfileType);
2219
} while (i < sub.Directory.Length);
2225
public class NefFile : TiffFile, IThumbnailContainer {
2226
public NefFile (string path) : base (path)
2230
public NefFile (Uri uri) : base (uri)
2234
public override void Select (SemWeb.StatementSink sink)
2236
DirectoryEntry e = Header.Directory.Lookup (TagId.NewSubfileType);
2243
ImageDirectory dir = Header.Directory;
2244
SubdirectoryEntry sub = (SubdirectoryEntry) dir.Lookup (TagId.ExifIfdPointer);
2247
Header.SelectDirectory (sub.Directory [0], sink);
2249
sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);
2253
uint dirtype = e.ValueAsLong [0];
2255
Header.SelectDirectory (dir, sink);
2262
dir = sub.Directory [i];
2264
e = dir.Lookup (TagId.NewSubfileType);
2266
} while (i < sub.Directory.Length);
2270
public Gdk.Pixbuf GetEmbeddedThumbnail ()
2272
using (System.IO.Stream stream = Open ()) {
2273
return TransformAndDispose (new Gdk.Pixbuf (stream));
2277
public override System.IO.Stream PixbufStream ()
2280
SubdirectoryEntry sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);
2281
ImageDirectory jpeg_directory = sub.Directory [0];
2282
return LookupJpegSubstream (jpeg_directory);
2283
} catch (System.Exception) {
2284
return DCRawFile.RawPixbufStream (uri);
2291
public class Cr2File : TiffFile, IThumbnailContainer {
2292
public Cr2File (string path) : base (path)
2296
public Cr2File (Uri uri) : base (uri)
2301
public override PixbufOrientation GetOrientation ()
2303
return PixbufOrientation.TopLeft;
2308
public Gdk.Pixbuf GetEmbeddedThumbnail ()
2310
ImageDirectory directory;
2311
directory = Header.Directory.NextDirectory;
2312
return TransformAndDispose (LoadJpegInterchangeFormat (directory));
2316
public override System.IO.Stream PixbufStream ()
2318
uint offset = Header.Directory.Lookup (TagId.StripOffsets).ValueAsLong [0];
2319
System.IO.Stream file = Open ();
2320
file.Position = offset;
2327
public class Tests {
2330
Gnome.Vfs.Vfs.Initialize ();
2331
Gtk.Application.Init ();
2337
string desc = "this is an example description";
2338
string desc2 = "\x00a9 Novell Inc.";
2339
PixbufOrientation orient = PixbufOrientation.TopRight;
2340
Gdk.Pixbuf test = new Gdk.Pixbuf (null, "f-spot-32.png");
2341
string path = ImageFile.TempPath ("joe.jpg");
2343
PixbufUtils.SaveJpeg (test, path, 75, new Exif.ExifData ());
2344
JpegFile jimg = new JpegFile (path);
2345
jimg.SetDescription (desc);
2346
jimg.SetOrientation (orient);
2347
jimg.SaveMetaData (path);
2348
JpegFile mod = new JpegFile (path);
2349
Assert.AreEqual (mod.Orientation, orient);
2350
Assert.AreEqual (mod.Description, desc);
2351
jimg.SetDescription (desc2);
2352
jimg.SaveMetaData (path);
2353
mod = new JpegFile (path);
2354
Assert.AreEqual (mod.Description, desc2);
2356
Header header = mod.ExifHeader;
2358
string tmp = "/home/lewing/test.tiff";
2359
if (File.Exists (tmp))
2361
Stream stream = File.Open (tmp, FileMode.Create, FileAccess.ReadWrite);
2362
Console.WriteLine ("XXXX saving tiff {0}", tmp);
2364
System.IO.MemoryStream stream = new System.IO.MemoryStream ();
2367
header.Dump ("source");
2368
header.Save (stream);
2369
stream.Position = 0;
2370
//System.Console.WriteLine ("----------------------------------------------LOADING TIFF");
2371
Header loader = new Header (stream);
2372
loader.Dump ("loader");
2374
CompareDirectories (header.Directory, loader.Directory);
2376
System.IO.File.Delete (path);
2379
private void CompareDirectories (ImageDirectory olddir, ImageDirectory newdir)
2381
Assert.AreEqual (olddir.Entries.Count, newdir.Entries.Count);
2382
for (int i = 0; i < olddir.Entries.Count; i++) {
2383
Assert.AreEqual (olddir.Entries [i].Id, newdir.Entries [i].Id);
2384
Assert.AreEqual (olddir.Entries [i].Type, newdir.Entries [i].Type);
2385
Assert.AreEqual (olddir.Entries [i].Count, newdir.Entries [i].Count);
2386
Assert.AreEqual (olddir.Entries [i].Length, newdir.Entries [i].Length);
2387
if (olddir.Entries [i] is SubdirectoryEntry) {
2388
SubdirectoryEntry oldsub = olddir.Entries [i] as SubdirectoryEntry;
2389
SubdirectoryEntry newsub = newdir.Entries [i] as SubdirectoryEntry;
2391
for (int j = 0; j < oldsub.Directory.Length; j++)
2392
CompareDirectories (oldsub.Directory [j], newsub.Directory [j]);