private void WriteIFD(MemoryStream stream, Dictionary <ExifTag, ExifProperty> ifd, IFD ifdtype, long tiffoffset, bool preserveMakerNote) { BitConverterEx conv = new BitConverterEx(BitConverterEx.SystemByteOrder, ByteOrder); // Create a queue of fields to write Queue <ExifProperty> fieldqueue = new Queue <ExifProperty>(); foreach (ExifProperty prop in ifd.Values) { if (prop.Tag != ExifTag.MakerNote) { fieldqueue.Enqueue(prop); } } // Push the maker note data to the end if (ifd.ContainsKey(ExifTag.MakerNote)) { fieldqueue.Enqueue(ifd[ExifTag.MakerNote]); } // Offset to start of field data from start of TIFF header uint dataoffset = (uint)(2 + ifd.Count * 12 + 4 + stream.Position - tiffoffset); uint currentdataoffset = dataoffset; long absolutedataoffset = stream.Position + (2 + ifd.Count * 12 + 4); bool makernotewritten = false; // Field count stream.Write(conv.GetBytes((ushort)ifd.Count), 0, 2); // Fields while (fieldqueue.Count != 0) { ExifProperty field = fieldqueue.Dequeue(); ExifInterOperability interop = field.Interoperability; uint fillerbytecount = 0; // Try to preserve the makernote data offset if (!makernotewritten && !makerNoteProcessed && makerNoteOffset != 0 && ifdtype == IFD.EXIF && field.Tag != ExifTag.MakerNote && interop.Data.Length > 4 && currentdataoffset + interop.Data.Length > makerNoteOffset && ifd.ContainsKey(ExifTag.MakerNote)) { // Delay writing this field until we write the creator's note data fieldqueue.Enqueue(field); continue; } else if (field.Tag == ExifTag.MakerNote) { makernotewritten = true; // We may need to write filler bytes to preserve maker note offset if (preserveMakerNote && !makerNoteProcessed && (makerNoteOffset > currentdataoffset)) { fillerbytecount = makerNoteOffset - currentdataoffset; } else { fillerbytecount = 0; } } // Tag stream.Write(conv.GetBytes(interop.TagID), 0, 2); // Type stream.Write(conv.GetBytes(interop.TypeID), 0, 2); // Count stream.Write(conv.GetBytes(interop.Count), 0, 4); // Field data byte[] data = interop.Data; if (ByteOrder != BitConverterEx.SystemByteOrder && (interop.TypeID == 3 || interop.TypeID == 4 || interop.TypeID == 9 || interop.TypeID == 5 || interop.TypeID == 10)) { int vlen = 4; if (interop.TypeID == 3) { vlen = 2; } int n = data.Length / vlen; for (int i = 0; i < n; i++) { Array.Reverse(data, i * vlen, vlen); } } // Fields containing offsets to other IFDs // Just store their offsets, we will write the values later on when we know the lengths of IFDs if (ifdtype == IFD.Zeroth && interop.TagID == 0x8769) { exifIFDFieldOffset = stream.Position; } else if (ifdtype == IFD.Zeroth && interop.TagID == 0x8825) { gpsIFDFieldOffset = stream.Position; } else if (ifdtype == IFD.EXIF && interop.TagID == 0xa005) { interopIFDFieldOffset = stream.Position; } else if (ifdtype == IFD.First && interop.TagID == 0x201) { thumbOffsetLocation = stream.Position; } else if (ifdtype == IFD.First && interop.TagID == 0x202) { thumbSizeLocation = stream.Position; } // Write 4 byte field value or field data if (data.Length <= 4) { stream.Write(data, 0, data.Length); for (int i = data.Length; i < 4; i++) { stream.WriteByte(0); } } else { // Pointer to data area relative to TIFF header stream.Write(conv.GetBytes(currentdataoffset + fillerbytecount), 0, 4); // Actual data long currentoffset = stream.Position; stream.Seek(absolutedataoffset, SeekOrigin.Begin); // Write filler bytes for (int i = 0; i < fillerbytecount; i++) { stream.WriteByte(0xFF); } stream.Write(data, 0, data.Length); stream.Seek(currentoffset, SeekOrigin.Begin); // Increment pointers currentdataoffset += fillerbytecount + (uint)data.Length; absolutedataoffset += fillerbytecount + data.Length; } } // Offset to 1st IFD // We will write zeros for now. This will be filled after we write all IFDs if (ifdtype == IFD.Zeroth) { firstIFDFieldOffset = stream.Position; } stream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Seek to end of IFD stream.Seek(absolutedataoffset, SeekOrigin.Begin); // Write thumbnail data if (ifdtype == IFD.First) { if (Thumbnail != null) { MemoryStream ts = new MemoryStream(); Thumbnail.Save(ts); ts.Close(); byte[] thumb = ts.ToArray(); thumbOffsetValue = (uint)(stream.Position - tiffoffset); thumbSizeValue = (uint)thumb.Length; stream.Write(thumb, 0, thumb.Length); ts.Dispose(); } else { thumbOffsetValue = 0; thumbSizeValue = 0; } } }
public ExifPropertyDescriptor(ExifProperty property) : base(property.Name, new Attribute[] { new BrowsableAttribute(true) }) { linkedProperty = property; originalValue = property.Value; }
/// <summary> /// Reads the APP1 section containing Exif metadata. /// </summary> private void ReadExifAPP1() { // Find the APP1 section containing Exif metadata exifApp1 = Sections.Find(a => (a.Marker == JPEGMarker.APP1) && a.Header.Length >= 6 && (Encoding.ASCII.GetString(a.Header, 0, 6) == "Exif\0\0")); // If there is no APP1 section, add a new one after the last APP0 section (if any). if (exifApp1 == null) { int insertionIndex = Sections.FindLastIndex(a => a.Marker == JPEGMarker.APP0); if (insertionIndex == -1) { insertionIndex = 0; } insertionIndex++; exifApp1 = new JPEGSection(JPEGMarker.APP1); Sections.Insert(insertionIndex, exifApp1); if (BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian) { ByteOrder = BitConverterEx.ByteOrder.LittleEndian; } else { ByteOrder = BitConverterEx.ByteOrder.BigEndian; } return; } byte[] header = exifApp1.Header; SortedList <int, IFD> ifdqueue = new SortedList <int, IFD>(); makerNoteOffset = 0; // TIFF header int tiffoffset = 6; if (header[tiffoffset] == 0x49 && header[tiffoffset + 1] == 0x49) { ByteOrder = BitConverterEx.ByteOrder.LittleEndian; } else if (header[tiffoffset] == 0x4D && header[tiffoffset + 1] == 0x4D) { ByteOrder = BitConverterEx.ByteOrder.BigEndian; } else { throw new NotValidExifFileException(); } // TIFF header may have a different byte order BitConverterEx.ByteOrder tiffByteOrder = ByteOrder; if (BitConverterEx.LittleEndian.ToUInt16(header, tiffoffset + 2) == 42) { tiffByteOrder = BitConverterEx.ByteOrder.LittleEndian; } else if (BitConverterEx.BigEndian.ToUInt16(header, tiffoffset + 2) == 42) { tiffByteOrder = BitConverterEx.ByteOrder.BigEndian; } else { throw new NotValidExifFileException(); } // Offset to 0th IFD int ifd0offset = (int)BitConverterEx.ToUInt32(header, tiffoffset + 4, tiffByteOrder, BitConverterEx.SystemByteOrder); ifdqueue.Add(ifd0offset, IFD.Zeroth); BitConverterEx conv = new BitConverterEx(ByteOrder, BitConverterEx.SystemByteOrder); int thumboffset = -1; int thumblength = 0; int thumbtype = -1; // Read IFDs while (ifdqueue.Count != 0) { int ifdoffset = tiffoffset + ifdqueue.Keys[0]; IFD currentifd = ifdqueue.Values[0]; ifdqueue.RemoveAt(0); // Field count ushort fieldcount = conv.ToUInt16(header, ifdoffset); for (short i = 0; i < fieldcount; i++) { // Read field info int fieldoffset = ifdoffset + 2 + 12 * i; ushort tag = conv.ToUInt16(header, fieldoffset); ushort type = conv.ToUInt16(header, fieldoffset + 2); uint count = conv.ToUInt32(header, fieldoffset + 4); byte[] value = new byte[4]; Array.Copy(header, fieldoffset + 8, value, 0, 4); // Fields containing offsets to other IFDs if (currentifd == IFD.Zeroth && tag == 0x8769) { int exififdpointer = (int)conv.ToUInt32(value, 0); ifdqueue.Add(exififdpointer, IFD.EXIF); } else if (currentifd == IFD.Zeroth && tag == 0x8825) { int gpsifdpointer = (int)conv.ToUInt32(value, 0); ifdqueue.Add(gpsifdpointer, IFD.GPS); } else if (currentifd == IFD.EXIF && tag == 0xa005) { int interopifdpointer = (int)conv.ToUInt32(value, 0); ifdqueue.Add(interopifdpointer, IFD.Interop); } // Save the offset to maker note data if (currentifd == IFD.EXIF && tag == 37500) { makerNoteOffset = conv.ToUInt32(value, 0); } // Calculate the bytes we need to read uint baselength = 0; if (type == 1 || type == 2 || type == 7) { baselength = 1; } else if (type == 3) { baselength = 2; } else if (type == 4 || type == 9) { baselength = 4; } else if (type == 5 || type == 10) { baselength = 8; } uint totallength = count * baselength; // If field value does not fit in 4 bytes // the value field is an offset to the actual // field value int fieldposition = 0; if (totallength > 4) { fieldposition = tiffoffset + (int)conv.ToUInt32(value, 0); value = new byte[totallength]; Array.Copy(header, fieldposition, value, 0, totallength); } // Compressed thumbnail data if (currentifd == IFD.First && tag == 0x201) { thumbtype = 0; thumboffset = (int)conv.ToUInt32(value, 0); } else if (currentifd == IFD.First && tag == 0x202) { thumblength = (int)conv.ToUInt32(value, 0); } // Uncompressed thumbnail data if (currentifd == IFD.First && tag == 0x111) { thumbtype = 1; // Offset to first strip if (type == 3) { thumboffset = (int)conv.ToUInt16(value, 0); } else { thumboffset = (int)conv.ToUInt32(value, 0); } } else if (currentifd == IFD.First && tag == 0x117) { thumblength = 0; for (int j = 0; j < count; j++) { if (type == 3) { thumblength += (int)conv.ToUInt16(value, 0); } else { thumblength += (int)conv.ToUInt32(value, 0); } } } // Create the exif property from the interop data ExifProperty prop = ExifPropertyFactory.Get(tag, type, count, value, ByteOrder, currentifd, Encoding); Properties.Add(prop); } // 1st IFD pointer int firstifdpointer = (int)conv.ToUInt32(header, ifdoffset + 2 + 12 * fieldcount); if (firstifdpointer != 0) { ifdqueue.Add(firstifdpointer, IFD.First); } // Read thumbnail if (thumboffset != -1 && thumblength != 0 && Thumbnail == null) { if (thumbtype == 0) { using (MemoryStream ts = new MemoryStream(header, tiffoffset + thumboffset, thumblength)) { Thumbnail = ImageFile.FromStream(ts); } } } } }