public static void AreEqual(IfdData data1, IfdData data2, string message) { message = message + " "; Assert.AreEqual(data1.NextIfdPointer, data2.NextIfdPointer, message + "Next IFD pointer"); Assert.AreEqual(data1.Length, data2.Length, message + "length"); Assert.AreEqual(data1.Entries.Count, data2.Entries.Count, message + "entry num"); }
/// <summary> /// Parse IFD section of Jpeg's header. /// </summary> /// <param name="App1Data">Raw data of App1 section</param> /// <param name="IfdOffset">Offset to the target IFD section from start of App1 data.</param> /// <param name="IfdSectionEndian">Alignment of all sections of this IFD data. This value is contained in TIFF header.</param> /// <returns>All entries in given IFD section.</returns> public static IfdData ParseIfd(byte[] App1Data, UInt32 IfdOffset, Definitions.Endian IfdSectionEndian) { var ifd = new IfdData(); ifd.Offset = IfdOffset; var entries = new Dictionary<UInt32, Entry>(); var EntryNum = Util.GetUIntValue(App1Data, (int)IfdOffset, 2, IfdSectionEndian); // Debug.WriteLine("Entry num: " + EntryNum); ifd.NextIfdPointer = Util.GetUIntValue(App1Data, (int)IfdOffset + 2 + (int)EntryNum * ENTRY_SIZE, 4, IfdSectionEndian); // if there's no extra data area, (if all data is 4 bytes or less), this is length of this IFD section. ifd.Length = 2 + EntryNum * ENTRY_SIZE + 4; // entry num (2 bytes), each entries (12 bytes each), Next IFD pointer (4 byte) for (int i = 0; i < EntryNum; i++) { // Debug.WriteLine("--- Entry[" + i + "] ---"); var EntryOrigin = (int)IfdOffset + 2 + i * ENTRY_SIZE; var entry = new Entry(); // tag entry.Tag = Util.GetUIntValue(App1Data, EntryOrigin, 2, IfdSectionEndian); var tagTypeName = "Unknown"; if (Util.TagNames.ContainsKey(entry.Tag)) { tagTypeName = Util.TagNames[entry.Tag]; } // Debug.WriteLine("Tag: " + entry.Tag.ToString("X") + " " + tagTypeName); // type var typeValue = Util.GetUIntValue(App1Data, EntryOrigin + 2, 2, IfdSectionEndian); entry.Type = Util.ToEntryType(typeValue); // Debug.WriteLine("Type: " + entry.Type.ToString()); // count entry.Count = Util.GetUIntValue(App1Data, EntryOrigin + 4, 4, IfdSectionEndian); // Debug.WriteLine("Count: " + entry.Count); var valueSize = 0; valueSize = Util.FindDataSize(entry.Type); var TotalValueSize = valueSize * (int)entry.Count; // Debug.WriteLine("Total value size: " + TotalValueSize); var valueBuff = new byte[TotalValueSize]; if (TotalValueSize <= 4) { // in this case, the value is stored directly here. Array.Copy(App1Data, EntryOrigin + 8, valueBuff, 0, TotalValueSize); } else { // other cases, actual value is stored in separated area var EntryValuePointer = (int)Util.GetUIntValue(App1Data, EntryOrigin + 8, 4, IfdSectionEndian); // Debug.WriteLine("Entry pointer: " + EntryValuePointer.ToString("X")); Array.Copy(App1Data, EntryValuePointer, valueBuff, 0, TotalValueSize); // If there's extra data, its length should be added to total length. ifd.Length += (UInt32)TotalValueSize; } if (IfdSectionEndian != Entry.InternalEndian) { // to change endian, each sections should be reversed. var ReversedValue = new byte[valueBuff.Length]; var valueLength = Util.FindDataSize(entry.Type); if (valueLength == 8) { // for fraction value, each value should be reversed individually valueLength = 4; } var valueNum = valueBuff.Length / valueLength; for (int j = 0; j < valueNum; j++) { var tempValue = new byte[valueLength]; Array.Copy(valueBuff, j * valueLength, tempValue, 0, valueLength); Array.Reverse(tempValue); Array.Copy(tempValue, 0, ReversedValue, j * valueLength, valueLength); } entry.value = ReversedValue; } else { // if internal endian and target metadata's one is same, no need to reverse. entry.value = valueBuff; } switch (entry.Type) { case Entry.EntryType.Ascii: // Debug.WriteLine("value: " + entry.StringValue + Environment.NewLine + Environment.NewLine); // Debug.WriteLine(" "); break; case Entry.EntryType.Byte: case Entry.EntryType.Undefined: if (entry.Tag == 0x927C) { // Debug.WriteLine("Maker note is too long to print."); } else { foreach (int val in entry.UIntValues) { // Debug.WriteLine("value: " + val.ToString("X")); } } break; case Entry.EntryType.Short: case Entry.EntryType.SShort: case Entry.EntryType.Long: case Entry.EntryType.SLong: // Util.DumpByteArrayAll(entry.value); foreach (int val in entry.UIntValues) { // Debug.WriteLine("value: " + val); } break; case Entry.EntryType.Rational: case Entry.EntryType.SRational: // Util.DumpByteArrayAll(entry.value); foreach (double val in entry.DoubleValues) { // Debug.WriteLine("value: " + val); } break; default: break; } entries[entry.Tag] = entry; } ifd.Entries = entries; return ifd; }
public static IfdData CreateGpsIfdData(Geoposition position) { var gpsIfdData = new IfdData(); gpsIfdData.Entries = new Dictionary<UInt32, Entry>(); var GPSVersionEntry = new Entry() { Tag = 0x0, Type = Entry.EntryType.Byte, Count = 4, }; GPSVersionEntry.UIntValues = new UInt32[] { 2, 2, 0, 0 }; gpsIfdData.Entries.Add(GPSVersionEntry.Tag, GPSVersionEntry); var LatitudeRefEntry = new Entry() { Tag = 0x1, Type = Entry.EntryType.Ascii, Count = 2, }; char latRef; double latitude = 0; #if (WINDOWS_APP || WINDOWS_UWP) latitude = position.Coordinate.Point.Position.Latitude; #elif WINDOWS_PHONE latitude = position.Coordinate.Latitude; #endif if (latitude > 0) { latRef = 'N'; } else { latRef = 'S'; latitude *= -1; } LatitudeRefEntry.UIntValues = new UInt32[] { (byte)latRef, 0 }; gpsIfdData.Entries.Add(LatitudeRefEntry.Tag, LatitudeRefEntry); var LatitudeEntry = new Entry() { Tag = 0x2, Type = Entry.EntryType.Rational, Count = 3, }; var LatDeg = Math.Floor(latitude); var LatMin = Math.Floor((latitude - LatDeg) * 60); var LatSec = Util.ToRoundUp(((latitude - LatDeg) * 60 - LatMin) * 60, 2); Debug.WriteLine("Latitude: " + LatDeg + " " + LatMin + " " + LatSec); try { LatitudeEntry.DoubleValues = new double[] { LatDeg, LatMin, LatSec }; } catch (OverflowException) { var sec = Util.ToRoundUp(((latitude - LatDeg) * 60 - LatMin) * 60, 0); Debug.WriteLine("Latitude: " + LatDeg + " " + LatMin + " " + sec); LatitudeEntry.DoubleValues = new double[] { LatDeg, LatMin, sec }; } gpsIfdData.Entries.Add(LatitudeEntry.Tag, LatitudeEntry); var LongitudeRef = new Entry() { Tag = 0x3, Type = Entry.EntryType.Ascii, Count = 3, }; char lonRef; double longitude = 0; #if (WINDOWS_APP || WINDOWS_UWP) longitude = position.Coordinate.Point.Position.Longitude; #elif WINDOWS_PHONE longitude = position.Coordinate.Longitude; #endif if (longitude > 0) { lonRef = 'E'; } else { lonRef = 'W'; longitude *= -1; } LongitudeRef.UIntValues = new UInt32[] { (byte)lonRef, 0 }; gpsIfdData.Entries.Add(LongitudeRef.Tag, LongitudeRef); var Longitude = new Entry() { Tag = 0x4, Type = Entry.EntryType.Rational, Count = 3, }; var LonDeg = Math.Floor(longitude); var LonMin = Math.Floor((longitude - LonDeg) * 60); var LonSec = Util.ToRoundUp(((longitude - LonDeg) * 60 - LonMin) * 60, 2); Debug.WriteLine("Longitude: " + LonDeg + " " + LonMin + " " + LonSec); try { Longitude.DoubleValues = new double[] { LonDeg, LonMin, LonSec }; } catch (OverflowException) { var sec = Util.ToRoundUp(((longitude - LonDeg) * 60 - LonMin) * 60, 0); Debug.WriteLine("Longitude: " + LonDeg + " " + LonMin + " " + sec); Longitude.DoubleValues = new double[] { LonDeg, LonMin, sec }; } gpsIfdData.Entries.Add(Longitude.Tag, Longitude); var TimeStampEntry = new Entry() { Tag = 0x7, Type = Entry.EntryType.Rational, Count = 3, }; TimeStampEntry.DoubleValues = new double[] { position.Coordinate.Timestamp.Hour, position.Coordinate.Timestamp.Minute, position.Coordinate.Timestamp.Second }; gpsIfdData.Entries.Add(TimeStampEntry.Tag, TimeStampEntry); var GpsMapDetum = new Entry() { Tag = 0x12, Type = Entry.EntryType.Ascii, Count = 7, }; GpsMapDetum.value = Util.ToByte("WGS-84"); gpsIfdData.Entries.Add(GpsMapDetum.Tag, GpsMapDetum); var GpsDateStamp = new Entry() { Tag = 0x1D, Type = Entry.EntryType.Ascii, Count = 11, }; String str = position.Coordinate.Timestamp.Year.ToString("D4") + ":" + position.Coordinate.Timestamp.Month.ToString("D2") + ":" + position.Coordinate.Timestamp.Day.ToString("D2"); GpsDateStamp.value = Util.ToByte(str); gpsIfdData.Entries.Add(GpsDateStamp.Tag, GpsDateStamp); return gpsIfdData; }
/// <summary> /// Build byte data from IFD data. /// </summary> /// <param name="ifd">IFD structure which will be composed to binary data.</param> /// <param name="SectionOffset">Offset of this IFD section from TIFF header.</param> /// <returns></returns> public static byte[] ComposeIfdsection(IfdData ifd, Definitions.Endian MetadataEndian) { var data = ifd.Entries; // calcurate total size of IFD var TotalSize = 2; // TIFF HEader + number of entry UInt32 count = 0; foreach (Entry entry in data.Values) { count++; TotalSize += 12; // if value is more than 4 bytes, need separated section to store all data. if (entry.value.Length > 4) { TotalSize += entry.value.Length; } } // area for pointer to next IFD section. TotalSize += 4; var ComposedData = new byte[TotalSize]; // set data of entry num. var EntryNum = Util.ToByte(count, 2, MetadataEndian); Array.Copy(EntryNum, ComposedData, EntryNum.Length); // set Next IFD pointer var ifdPointerValue = Util.ToByte(ifd.NextIfdPointer, 4, MetadataEndian); // Debug.WriteLine("Nexf IFD: " + ifd.NextIfdPointer.ToString("X")); Array.Copy(ifdPointerValue, 0, ComposedData, 2 + 12 * (int)count, 4); // TIFF header, number of entry, each entries, Nexf IFD pointer. var ExtraDataSectionOffset = (UInt32)(2 + 12 * (int)count + 4); // Debug.WriteLine("ExtraDataSectionOffset: " + ExtraDataSectionOffset.ToString("X")); // Debug.WriteLine("ifd.offset: " + ifd.Offset.ToString("X")); var keys = data.Keys.ToArray<UInt32>(); Array.Sort(keys); int pointer = 2; foreach (UInt32 key in keys) { // tag in 2 bytes. var tag = Util.ToByte(data[key].Tag, 2, MetadataEndian); Array.Copy(tag, 0, ComposedData, pointer, 2); pointer += 2; // Debug.WriteLine("Tag: " + data[key].Tag.ToString("X")); // type var type = Util.ToByte(Util.ToUInt32(data[key].Type), 2, MetadataEndian); Array.Copy(type, 0, ComposedData, pointer, 2); pointer += 2; // count var c = Util.ToByte(data[key].Count, 4, MetadataEndian); Array.Copy(c, 0, ComposedData, pointer, 4); pointer += 4; if (data[key].value.Length <= 4) { // upto 4 bytes, copy value directly. Array.Copy(data[key].value, 0, ComposedData, pointer, data[key].value.Length); } else { // save actual data to extra area Array.Copy(data[key].value, 0, ComposedData, (int)ExtraDataSectionOffset, data[key].value.Length); // store pointer for extra area. Origin of pointer should be position of TIFF header. var offset = Util.ToByte(ExtraDataSectionOffset + ifd.Offset, 4, MetadataEndian); Array.Copy(offset, 0, ComposedData, pointer, 4); // Util.DumpFirst16byte(offset); // Util.DumpByteArray(ComposedData, pointer, 4); ExtraDataSectionOffset += (UInt32)data[key].value.Length; } pointer += 4; } // Util.DumpByteArrayAll(ComposedData); return ComposedData; }