/// <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; }
/// <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; }
/// <summary> /// Create app1 data section in byte array. /// Needs many informations.... /// </summary> /// <param name="OriginalMetaData">metadata from original image.</param> /// <param name="NewMetaData">metadata which will be recorded in app1 data section</param> /// <param name="OriginalApp1Data">Original app1 data. Other than Exif meta data will be copied to new one</param> /// <param name="OutputImageMetadataEndian">Endian which will be used in Exif meta data. for WP OS, Big endian is recommended.</param> /// <returns></returns> private static byte[] CreateApp1Data(JpegMetaData OriginalMetaData, JpegMetaData NewMetaData, byte[] OriginalApp1Data, Definitions.Endian OutputImageMetadataEndian) { if (NewMetaData.ExifIfd != null && !NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.EXIF_IFD_POINTER_TAG)) { // Add Exif IFD section's pointer entry as dummy. NewMetaData.PrimaryIfd.Entries.Add( Definitions.EXIF_IFD_POINTER_TAG, new Entry() { Tag = Definitions.EXIF_IFD_POINTER_TAG, Type = Entry.EntryType.Long, Count = 1, value = new byte[] { 0, 0, 0, 0 } }); } if (NewMetaData.GpsIfd != null && !NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG)) { // Add GPS IFD section's pointer entry as dummy NewMetaData.PrimaryIfd.Entries.Add( Definitions.GPS_IFD_POINTER_TAG, new Entry() { Tag = Definitions.GPS_IFD_POINTER_TAG, Type = Entry.EntryType.Long, Count = 1, value = new byte[] { 0, 0, 0, 0 } }); } byte[] primaryIfd = IfdComposer.ComposeIfdsection(NewMetaData.PrimaryIfd, OutputImageMetadataEndian); byte[] exifIfd = new byte[] { }; byte[] gpsIfd = new byte[] { }; if (NewMetaData.ExifIfd != null) { exifIfd = IfdComposer.ComposeIfdsection(NewMetaData.ExifIfd, OutputImageMetadataEndian); } if (NewMetaData.GpsIfd != null) { gpsIfd = IfdComposer.ComposeIfdsection(NewMetaData.GpsIfd, OutputImageMetadataEndian); } // Debug.WriteLine("Size fixed. Primary: " + primaryIfd.Length.ToString("X") + " exif: " + exifIfd.Length.ToString("X") + " gps: " + gpsIfd.Length.ToString("X")); // after size fixed, set each IFD sections' offset. NewMetaData.PrimaryIfd.Offset = 8; // fixed value // now it's possible to calcurate pointer to Exif/GPS IFD var exifIfdPointer = 8 + primaryIfd.Length; var gpsIfdPointer = 8 + primaryIfd.Length + exifIfd.Length; if (NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.EXIF_IFD_POINTER_TAG)) { NewMetaData.PrimaryIfd.Entries.Remove(Definitions.EXIF_IFD_POINTER_TAG); } var exifIfdPointerEntry = new Entry() { Tag = Definitions.EXIF_IFD_POINTER_TAG, Type = Entry.EntryType.Long, Count = 1, }; exifIfdPointerEntry.UIntValues = new UInt32[] { (UInt32)exifIfdPointer }; NewMetaData.PrimaryIfd.Entries.Add(Definitions.EXIF_IFD_POINTER_TAG, exifIfdPointerEntry); if (NewMetaData.PrimaryIfd.Entries.ContainsKey(Definitions.GPS_IFD_POINTER_TAG)) { NewMetaData.PrimaryIfd.Entries.Remove(Definitions.GPS_IFD_POINTER_TAG); } var gpsIfdPointerEntry = new Entry() { Tag = Definitions.GPS_IFD_POINTER_TAG, Type = Entry.EntryType.Long, Count = 1, }; gpsIfdPointerEntry.UIntValues = new UInt32[] { (UInt32)gpsIfdPointer }; NewMetaData.PrimaryIfd.Entries.Add(Definitions.GPS_IFD_POINTER_TAG, gpsIfdPointerEntry); var nextIfdPointer = 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length; NewMetaData.PrimaryIfd.NextIfdPointer = (UInt32)nextIfdPointer; // finally, create byte array again primaryIfd = IfdComposer.ComposeIfdsection(NewMetaData.PrimaryIfd, OutputImageMetadataEndian); if (NewMetaData.ExifIfd != null) { NewMetaData.ExifIfd.Offset = 8 + (UInt32)primaryIfd.Length; exifIfd = IfdComposer.ComposeIfdsection(NewMetaData.ExifIfd, OutputImageMetadataEndian); } if (NewMetaData.GpsIfd != null) { NewMetaData.GpsIfd.Offset = 8 + (UInt32)primaryIfd.Length + (UInt32)exifIfd.Length; gpsIfd = IfdComposer.ComposeIfdsection(NewMetaData.GpsIfd, OutputImageMetadataEndian); } // 1st IFD data (after primary IFD data) should be kept var Original1stIfdData = new byte[OriginalApp1Data.Length - OriginalMetaData.PrimaryIfd.NextIfdPointer]; Array.Copy(OriginalApp1Data, (int)OriginalMetaData.PrimaryIfd.NextIfdPointer, Original1stIfdData, 0, Original1stIfdData.Length); // Build App1 section. From "Exif\0\0" to end of thumbnail data (1st IFD) // Exif00 + TIFF header + 3 IFD sections (Primary, Exif, GPS) + 1st IFD data from original data // new app1 size may overfrow from 0xFFFF bytes. in this case, it should be limited up to 0xFFFF byte. var NewApp1DataSize = Math.Min(0xFFFF, 6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length + Original1stIfdData.Length); var NewApp1DataOverflowSize = 0; if (NewApp1DataSize == 0xFFFF) { NewApp1DataOverflowSize = (6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length + Original1stIfdData.Length) - 0xFFFF; Debug.WriteLine("App1 overflow size: " + NewApp1DataOverflowSize); } var NewApp1Data = new byte[NewApp1DataSize]; // var NewApp1Data = new byte[6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length]; // Debug.WriteLine("New App1 size: " + NewApp1Data.Length.ToString("X")); // Array.Copy(OriginalApp1Data, 0, NewApp1Data, 0, 6 + 8); Array.Copy(OriginalApp1Data, 0, NewApp1Data, 0, 6); // only EXIF00 should be copied. var endianSection = Util.ToByte(0x4d4d, 2, OutputImageMetadataEndian); Array.Copy(endianSection, 0, NewApp1Data, 6, 2); var magicNumber = Util.ToByte(0x002A, 2, OutputImageMetadataEndian); Array.Copy(magicNumber, 0, NewApp1Data, 8, 2); var primaryIfdOffset = Util.ToByte(8, 4, OutputImageMetadataEndian); Array.Copy(primaryIfdOffset, 0, NewApp1Data, 10, 4); Array.Copy(primaryIfd, 0, NewApp1Data, 6 + 8, primaryIfd.Length); Array.Copy(exifIfd, 0, NewApp1Data, 6 + 8 + primaryIfd.Length, exifIfd.Length); Array.Copy(gpsIfd, 0, NewApp1Data, 6 + 8 + primaryIfd.Length + exifIfd.Length, gpsIfd.Length); Array.Copy(Original1stIfdData, 0, NewApp1Data, 6 + 8 + primaryIfd.Length + exifIfd.Length + gpsIfd.Length, Original1stIfdData.Length - NewApp1DataOverflowSize); return NewApp1Data; }
public static byte[] ToByte(SignedFraction value, Definitions.Endian endian) { var ret = new byte[8]; Array.Copy(Util.ToByte(value.Numerator, 4, endian), 0, ret, 0, 4); Array.Copy(Util.ToByte(value.Denominator, 4, endian), 0, ret, 4, 4); return ret; }
public static byte[] ToByte(Int32 value, int length, Definitions.Endian endian) { if (length < 1 || length > 4) { throw new InvalidCastException(); } var max = (Int64)(Math.Pow(2, (length * 8)) / 2 - 1); var min = -(max + 1); if (value > max) { throw new OverflowException(); } else if (value < min) { throw new InvalidCastException(); } var ByteValue = new byte[length]; if (value < 0) { ByteValue = ToByte((UInt32)(-value), length, endian); for (int i = 0; i < ByteValue.Length; i++) { ByteValue[i] = (byte)~(ByteValue[i]); } UInt32 temp = Util.GetUIntValue(ByteValue, 0, length, endian) + 1; ByteValue = Util.ToByte(temp, length, endian); } else { ByteValue = ToByte((UInt32)value, length, endian); } return ByteValue; }
public static byte[] ToByte(UInt32 value, int length, Definitions.Endian endian) { if (length > 4 || length <= 0) { throw new InvalidCastException(); } var ret = new byte[length]; if (endian == Definitions.Endian.Little) { for (int i = 0; i < length; i++) { ret[i] = (byte)(value & 0xFF); value = value >> 8; } } else { for (int i = length - 1; i >= 0; i--) { ret[i] = (byte)(value & 0xFF); value = value >> 8; // Debug.WriteLine("byte: " + ret[i].ToString("X")); } } // after conversion, remaining value must be 0. if (value > 0) { throw new OverflowException(); } return ret; }
public static UInt32 GetUIntValue(byte[] data, int address, int length, Definitions.Endian endian) { // if bigger than 4 bytes, can't set to int type. if (length > 4 || length <= 0) { throw new InvalidCastException("Uint32 type can't store more than 4 bytes"); } UInt32 value = 0; for (int i = 0; i < length; i++) { // Debug.WriteLine(data[address + i].ToString("X")); if (endian == Definitions.Endian.Little) { value += (UInt32)data[address + i] << (i * 8); } else { value += (UInt32)data[address + i] << ((length - 1 - i) * 8); } } // Debug.WriteLine("Return " + value.ToString("X")); return value; }
public static Int32 GetSIntValue(byte[] data, int address, int length, Definitions.Endian endian) { // if bigger than 4 bytes, can't set to int type. if (length > 4) { throw new InvalidCastException("Uint32 type can't store more than 4 bytes"); } if (length < 1) { throw new InvalidCastException(); } if (address >= data.Length) { throw new IndexOutOfRangeException(); } Int32 value = 0; bool IsNegative = false; var TempValue = new byte[length]; Array.Copy(data, address, TempValue, 0, length); // if highest bit is set, it's negative value. if (endian == Definitions.Endian.Big) { if ((TempValue[0] & 0x80) == 0x80) { IsNegative = true; } // Debug.WriteLine("negative number: " + TempValue[length - 1]); } else { if ((TempValue[length - 1] & 0x80) == 0x80) { IsNegative = true; // Debug.WriteLine("negative number: " + TempValue[length - 1]); } } if (IsNegative) { for (int i = 0; i < length; i++) { TempValue[i] = (byte)~TempValue[i]; } } for (int i = 0; i < length; i++) { // Debug.WriteLine(data[address + i].ToString("X")); if (endian == Definitions.Endian.Little) { value += (Int32)TempValue[i] << (i * 8); // Debug.WriteLine("value: " + value); } else { value += (Int32)TempValue[i] << ((length - 1 - i) * 8); } } // Debug.WriteLine("Return " + value.ToString("X")); if (IsNegative) { value += 1; value = -value; } return value; }