public override void Parse(Stream file) { base.Parse(file); Tag subifdoffsetTag; if (!ifd.tags.TryGetValue(0x14A, out subifdoffsetTag)) { throw new FormatException("File not correct"); } subifd = new IFD[subifdoffsetTag.dataCount]; for (int i = 0; i < subifdoffsetTag.dataCount; i++) { subifd[i] = new IFD(fileStream, (uint)subifdoffsetTag.data[i], true, false); } //get the Exif Tag exifoffsetTag; if (!ifd.tags.TryGetValue(0x8769, out exifoffsetTag)) { throw new FormatException("File not correct"); } //todo third IFD exif = new IFD(fileStream, (uint)exifoffsetTag.data[0], true, false); Tag makerNoteOffsetTag; if (!exif.tags.TryGetValue(0x927C, out makerNoteOffsetTag)) { throw new FormatException("File not correct"); } makerNote = new NikonMakerNote(fileStream, makerNoteOffsetTag.dataOffset, true); }
/// <summary> /// Extracts the XMP packet. /// </summary> /// <param name="reader">The reader.</param> /// <returns>The extracted XMP packet.</returns> /// <exception cref="EndOfStreamException">The end of the stream has been reached.</exception> private static byte[] ExtractXMPPacket(EndianBinaryReader reader) { ushort signature = reader.ReadUInt16(); if (signature == TIFFSignature) { uint ifdOffset = reader.ReadUInt32(); reader.Position = ifdOffset; int ifdCount = reader.ReadUInt16(); for (int i = 0; i < ifdCount; i++) { IFD ifd = new IFD(reader); if (ifd.tag == XmpTag && (ifd.type == DataType.Byte || ifd.type == DataType.Undefined)) { if (ifd.count > int.MaxValue) { // The .NET Framework does not support arrays larger than 2 GB. return(null); } reader.Position = ifd.offset; return(reader.ReadBytes((int)ifd.count)); } } } return(null); }
public DE(byte[] de, FileStream fs, IFD fd) { Tag = (ushort)fd.Head.getValue(de.Take(2), 3); //BitConverter.ToUInt16(ifh.Bit == "MM" ? .Reverse().ToArray() : de.Take(2).ToArray(), 0); if (Tag != 256 && Tag != 257 && Tag != 273 && Tag != 279) { return; } Type = (ushort)fd.Head.getValue(de.Skip(2).Take(2), 3); //BitConverter.ToUInt16(ifh.Bit == "MM" ? de.Skip(2).Take(2).Reverse().ToArray() : de.Skip(2).Take(2).ToArray(), 0); Length = (uint)fd.Head.getValue(de.Skip(4).Take(4), 4); // BitConverter.ToUInt32(ifh.Bit == "MM" ? de.Skip(4).Take(4).Reverse().ToArray() : de.Skip(4).Take(4).ToArray(), 0); var v = Convert.ToInt64(fd.Head.getValue(de.Skip(8).Take(4), Type)); if (Length * Type > 4) { try { fs.Seek(v, SeekOrigin.Begin); var dt = new byte[Length * (ulong)tps[Type]]; fs.Read(dt, 0, dt.Length); Value = dt; } catch { } } else { Value = v; } }
public NikonMakerNote(TIFFBinaryReader buffer, uint offset, bool compression) { //read the header buffer.BaseStream.Position = offset; stringMagic = ""; this.offset = offset; for (int i = 0; i < 6; i++) { stringMagic += buffer.ReadChar(); } version = buffer.ReadUInt16(); buffer.BaseStream.Position = 2 + offset; //jump the padding header = new Header(buffer, 0); //0 car beggining of the stream if (header.byteOrder == 0x4D4D) { buffer = new TIFFBinaryReaderRE(buffer.BaseStream); //TODO see if need to move } ifd = new IFD(buffer, header.TIFFoffset + getOffset(), true, true); //ifd = new IFD(buffer, (uint)buffer.BaseStream.Position, true, true); Tag previewOffsetTag; if (ifd.tags.TryGetValue(17, out previewOffsetTag)) { preview = new IFD(buffer, (uint)previewOffsetTag.data[0] + getOffset(), true, false); } else { preview = null; //no preview in this file } }
/* Parse FUJI information */ /* It is a simpler form of Tiff IFD, so we add them as TiffEntries */ void ParseFuji(uint offset) { try { IFD tempIFD = new IFD(ifd.endian, ifd.Depth); ImageBinaryReaderBigEndian bytes = new ImageBinaryReaderBigEndian(stream, offset); uint entries = bytes.ReadUInt32(); if (entries > 255) { throw new RawDecoderException("Too many entries"); } for (int i = 0; i < entries; i++) { UInt16 tag = bytes.ReadUInt16(); uint length = bytes.ReadUInt16(); Tag t; // Set types of known tags switch (tag) { case 0x100: case 0x121: case 0x2ff0: t = new Tag((TagType)tag, TiffDataType.SHORT, length / 2); for (int k = 0; k < t.dataCount; k++) { t.data[k] = bytes.ReadUInt16(); } break; case 0xc000: // This entry seem to have swapped endianness: t = new Tag((TagType)tag, TiffDataType.LONG, length / 4); for (int k = 0; k < t.dataCount; k++) { t.data[k] = bytes.ReadUInt32(); } break; default: t = new Tag((TagType)tag, TiffDataType.UNDEFINED, length); for (int k = 0; k < t.dataCount; k++) { t.data[k] = bytes.ReadByte(); } break; } tempIFD.tags.Add(t.TagId, t); //bytes.ReadBytes((int)length); } ifd.subIFD.Add(tempIFD); } catch (IOException) { throw new RawDecoderException("IO error occurred during parsing. Skipping the rest"); } }
protected void Parse(uint offset) { //parse the ifd if (stream.Length < 16) { throw new RawDecoderException("Not a TIFF file (size too small)"); } Endianness endian = Endianness.Little; byte[] data = new byte[5]; stream.Position = offset; stream.Read(data, 0, 4); if (data[0] == 0x4D || data[1] == 0x4D) { //open binaryreader reader = new ImageBinaryReaderBigEndian(stream); endian = Endianness.Big; if (data[3] != 42 && data[3] != 0x4f) // ORF sometimes has 0x4f! { throw new RawDecoderException("Not a TIFF file (magic 42)"); } } else if (data[0] == 0x49 || data[1] == 0x49) { reader = new ImageBinaryReader(stream); if (data[2] != 42 && data[2] != 0x52 && data[2] != 0x55) // ORF has 0x52, RW2 0x55! { throw new RawDecoderException("Not a TIFF file (magic 42)"); } } else { throw new RawDecoderException("Not a TIFF file (ID)"); } reader.Position = offset + 4; var newIfd = new IFD(reader, reader.ReadUInt32(), endian, 0, (int)offset); if (ifd == null) { ifd = newIfd; } else { ifd.subIFD.Add(newIfd); } uint nextIFD = newIfd.NextOffset; while (nextIFD != 0) { ifd.subIFD.Add(new IFD(reader, nextIFD, endian, 0, (int)offset)); if (ifd.subIFD.Count > 100) { throw new RawDecoderException("TIFF file has too many Sub IFDs, probably broken"); } nextIFD = (ifd.subIFD[ifd.subIFD.Count - 1]).NextOffset; } }
/// <summary> /// Removes all items with the given IFD from the collection. /// </summary> /// <param name="ifd">The IFD section to remove.</param> public void Remove(IFD ifd) { List <T> toRemove = new List <T>(); foreach (T item in items) { if (item.IFD == ifd) { toRemove.Add(item); } } Remove(toRemove); }
private void ParseData(byte[] data) { EndianFlag = Encoding.GetEncoding("ISO-8859-1").GetString(data.Take(2).ToArray()); int index = 2; if (Converter.ToInt16(data, index) != 42) { throw new FormatException("eXIf data is not in TIFF format"); } index += 2; Offset = Converter.ToInt32(data, index); Ifd = new IFD(data, Offset); }
/// <summary> /// Extracts the XMP packet from a TIFF file. /// </summary> /// <param name="stream">The stream to read.</param> /// <returns>The extracted XMP packet, or null.</returns> internal static byte[] ExtractXMP(Stream stream) { stream.Position = 0L; try { ushort byteOrder = ReadShort(stream, false); bool littleEndian = byteOrder == LittleEndianByteOrder; ushort signature = ReadShort(stream, littleEndian); if (signature == TIFFSignature) { uint ifdOffset = ReadLong(stream, littleEndian); stream.Seek(ifdOffset, SeekOrigin.Begin); int ifdCount = ReadShort(stream, littleEndian); for (int i = 0; i < ifdCount; i++) { IFD ifd = new IFD(stream, littleEndian); if (ifd.tag == XmpTag && (ifd.type == DataType.Byte || ifd.type == DataType.Undefined)) { stream.Seek(ifd.offset, SeekOrigin.Begin); int count = (int)ifd.count; byte[] xmpBytes = new byte[count]; int numBytesToRead = count; int numBytesRead = 0; do { int n = stream.Read(xmpBytes, numBytesRead, numBytesToRead); numBytesRead += n; numBytesToRead -= n; } while (numBytesToRead > 0); return(xmpBytes); } } } } catch (EndOfStreamException) { } return(null); }
void SetBlack(IFD raw) { if (raw.tags.ContainsKey(TagType.MASKEDAREAS)) { if (DecodeMaskedAreas(raw)) { return; } } if (raw.GetEntry(TagType.BLACKLEVEL) != null) { Decodeblacks(raw); } }
void DecodeUncompressed(IFD raw) { uint width = raw.GetEntry(TagType.IMAGEWIDTH).GetUInt(0); uint height = raw.GetEntry(TagType.IMAGELENGTH).GetUInt(0); uint off = raw.GetEntry(TagType.STRIPOFFSETS).GetUInt(0); rawImage.fullSize.dim = new Point2D(width, height); rawImage.Init(false); ImageBinaryReader input = new ImageBinaryReader(reader.BaseStream, off); /* * RawDecompressor.Decode14BitRawBEunpacked(input, width, height, rawImage); */ RawDecompressor.Decode16BitRawUnpacked(input, new Point2D(width, height), new Point2D(), rawImage); }
/// <summary> /// Removes all items with the given IFD from the collection. /// </summary> /// <param name="ifd">The IFD section to remove.</param> public void Remove(IFD ifd) { List <ExifProperty> toRemove = new List <ExifProperty>(); foreach (ExifProperty item in items) { if (item.IFD == ifd) { toRemove.Add(item); } } foreach (ExifProperty tag in toRemove) { items.Remove(tag); } }
/// <summary> /// Removes all items with the given IFD from the collection. /// </summary> /// <param name="ifd">The IFD section to remove.</param> public void Remove(IFD ifd) { List <ExifTag> toRemove = new List <ExifTag> (); foreach (KeyValuePair <ExifTag, ExifProperty> item in items) { if (item.Value.IFD == ifd) { toRemove.Add(item.Key); } } foreach (ExifTag tag in toRemove) { items.Remove(tag); } }
/// <summary> /// Retrieves a numbered tag from a specific IFD /// </summary> /// <remarks>Useful for cases where a new or non-standard tag isn't present in the <see cref="ExifTags"/> enumeration</remarks> public bool GetTagValue <T>(ushort tagID, IFD ifd, out T result) { Dictionary <ushort, long> catalogue; switch (ifd) { case IFD.IFD0: catalogue = _ifd0PrimaryCatalogue; break; default: throw new ArgumentOutOfRangeException(); } return(GetTagValue(catalogue, tagID, out result)); }
public override void DecodeRaw() { List <IFD> data = ifd.GetIFDsWithTag(TagType.STRIPOFFSETS); if (data.Count == 0) { throw new RawDecoderException("No image data found"); } IFD raw = data[0]; int compression = raw.GetEntry(TagType.COMPRESSION).GetInt(0); if (1 == compression || compression == 32773) { DecodeUncompressed(raw, BitOrder.Jpeg); return; } if (65535 != compression) { throw new RawDecoderException("Unsupported compression"); } Tag offsets = raw.GetEntry(TagType.STRIPOFFSETS); Tag counts = raw.GetEntry(TagType.STRIPBYTECOUNTS); if (offsets.dataCount != 1) { throw new RawDecoderException("Multiple Strips found: " + offsets.dataCount); } if (counts.dataCount != offsets.dataCount) { throw new RawDecoderException("Byte count number does not match strip size: count:" + counts.dataCount + ", strips:" + offsets.dataCount); } if (!reader.IsValid(offsets.GetUInt(0), counts.GetUInt(0))) { throw new RawDecoderException("Truncated file."); } rawImage.fullSize.dim = new Point2D(raw.GetEntry(TagType.IMAGEWIDTH).GetUInt(0), raw.GetEntry(TagType.IMAGELENGTH).GetUInt(0)); rawImage.Init(false); PentaxDecompressor l = new PentaxDecompressor(reader, rawImage); l.DecodePentax(ifd, offsets.GetUInt(0), counts.GetUInt(0)); reader.Dispose(); }
public override void Parse(Stream file) { //Open a binary stream on the file fileStream = new TIFFBinaryReader(file); //read the first bit to get the endianness of the file if (fileStream.ReadUInt16() == 0x4D4D) { //File is in reverse bit order // fileStream.Dispose(); //DO NOT dispose, because it remove the filestream not the reader and crash the parse fileStream = new TIFFBinaryReaderRE(file); } //read the header header = new Header(fileStream, 0); //Read the IFD ifd = new IFD(fileStream, header.TIFFoffset, true, false); }
public float GetFloat(IFD e1) { switch (e1.ty) { case 1: case 3: case 4: return((int)e1.val); case 5: { Int64 old = si.Position; si.Position = e1.val; float r1 = r.DWord(); float r2 = r.DWord(); si.Position = old; return((float)r1 / r2); } } throw new InvalidDataException(); }
/* Decodes DNG masked areas into blackareas in the image */ bool DecodeMaskedAreas(IFD raw) { Tag masked = raw.GetEntry(TagType.MASKEDAREAS); if (masked.dataType != TiffDataType.SHORT && masked.dataType != TiffDataType.LONG) { return(false); } Int32 nrects = (int)masked.dataCount / 4; if (0 == nrects) { return(false); } /* Since we may both have short or int, copy it to int array. */ var rects = masked.GetUIntArray(); Point2D top = rawImage.fullSize.offset; for (int i = 0; i < nrects; i++) { Point2D topleft = new Point2D(rects[i * 4 + 1], rects[i * 4]); Point2D bottomright = new Point2D(rects[i * 4 + 3], rects[i * 4 + 2]); // Is this a horizontal box, only add it if it covers the active width of the image if (topleft.width <= top.width && bottomright.width >= (rawImage.fullSize.dim.width + top.width)) { rawImage.blackAreas.Add(new BlackArea(topleft.height, bottomright.height - topleft.height, false)); } else if (topleft.height <= top.height && bottomright.height >= (rawImage.fullSize.dim.height + top.height)) { // Is it a vertical box, only add it if it covers the active height of the image rawImage.blackAreas.Add(new BlackArea(topleft.width, bottomright.width - topleft.width, true)); } } return(rawImage.blackAreas.Count != 0); }
/// <summary> /// Returns the string representation for the given tag id. /// </summary> public static string GetTagName(IFD ifd, ushort tagid) { return GetTagName(GetExifTag(ifd, tagid)); }
public override void DecodeRaw() { List <IFD> data = ifd.GetIFDsWithTag(TagType.STRIPOFFSETS); if (data.Count == 0) { Tag model = ifd.GetEntryRecursive(TagType.MODEL); if (model != null && model.DataAsString == "DSLR-A100") { DecodeA100(); return; } else { DecodeCryptedUncompressed(); return; } } IFD raw = data[0]; int compression = raw.GetEntry(TagType.COMPRESSION).GetInt(0); if (1 == compression) { DecodeUncompressed(raw); return; } if (32767 != compression) { throw new RawDecoderException("ARW Decoder: Unsupported compression"); } Tag offsets = raw.GetEntry(TagType.STRIPOFFSETS); Tag counts = raw.GetEntry(TagType.STRIPBYTECOUNTS); if (offsets.dataCount != 1) { throw new RawDecoderException("ARW Decoder: Multiple Strips found: " + offsets.dataCount); } if (counts.dataCount != offsets.dataCount) { throw new RawDecoderException("ARW Decoder: Byte count number does not match strip size: count:" + counts.dataCount + ", strips:%u " + offsets.dataCount); } uint width = raw.GetEntry(TagType.IMAGEWIDTH).GetUInt(0); uint height = raw.GetEntry(TagType.IMAGELENGTH).GetUInt(0); int bitPerPixel = raw.GetEntry(TagType.BITSPERSAMPLE).GetInt(0); rawImage.fullSize.ColorDepth = (ushort)bitPerPixel; // Sony E-550 marks compressed 8bpp ARW with 12 bit per pixel // this makes the compression detect it as a ARW v1. // This camera has however another MAKER entry, so we MAY be able // to detect it this way in the future. data = ifd.GetIFDsWithTag(TagType.MAKE); if (data.Count > 1) { for (Int32 i = 0; i < data.Count; i++) { string make = data[i].GetEntry(TagType.MAKE).DataAsString; // Check for maker "SONY" without spaces if (make != "SONY") { bitPerPixel = 8; } } } bool arw1 = counts.GetInt(0) * 8 != width * height * bitPerPixel; if (arw1) { height += 8; } rawImage.fullSize.dim = new Point2D(width, height); rawImage.Init(false); UInt16[] curve = new UInt16[0x4001]; Tag c = raw.GetEntry(TagType.SONY_CURVE); uint[] sony_curve = { 0, 0, 0, 0, 0, 4095 }; for (int i = 0; i < 4; i++) { sony_curve[i + 1] = (uint)(c.GetShort(i) >> 2) & 0xfff; } for (int i = 0; i < 0x4001; i++) { curve[i] = (ushort)i; } for (int i = 0; i < 5; i++) { for (uint j = sony_curve[i] + 1; j <= sony_curve[i + 1]; j++) { curve[j] = (ushort)(curve[j - 1] + (1 << i)); } } var table = new TableLookUp(curve, 0x4000, true); long c2 = counts.GetUInt(0); uint off = offsets.GetUInt(0); if (!reader.IsValid(off)) { throw new RawDecoderException("Sony ARW decoder: Data offset after EOF, file probably truncated"); } if (!reader.IsValid(off, c2)) { c2 = reader.BaseStream.Length - off; } ImageBinaryReader input = new ImageBinaryReader(reader.BaseStream, off); if (arw1) { DecodeARW(input, width, height); } else { DecodeARW2(input, width, height, bitPerPixel); } //table was already applyed rawImage.table = null; }
public ExifProperty(ExifTag tag) { mTag = tag; mIFD = ExifTagFactory.GetTagIFD(tag); }
/// <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) { var 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; } var header = _exifApp1.Header; var ifdqueue = new SortedList <int, IFD>(); _makerNoteOffset = 0; // TIFF header var 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 var ifd0offset = (int)BitConverterEx.ToUInt32(header, tiffoffset + 4, tiffByteOrder, BitConverterEx.SystemByteOrder); ifdqueue.Add(ifd0offset, IFD.Zeroth); var conv = new BitConverterEx(ByteOrder, BitConverterEx.SystemByteOrder); var thumboffset = -1; var thumblength = 0; var thumbtype = -1; // Read IFDs while (ifdqueue.Count != 0) { var ifdoffset = tiffoffset + ifdqueue.Keys[0]; IFD currentifd = ifdqueue.Values[0]; ifdqueue.RemoveAt(0); // Field count var fieldcount = conv.ToUInt16(header, ifdoffset); for (short i = 0; i < fieldcount; i++) { // Read field info var fieldoffset = ifdoffset + 2 + (12 * i); var tag = conv.ToUInt16(header, fieldoffset); var type = conv.ToUInt16(header, fieldoffset + 2); var count = conv.ToUInt32(header, fieldoffset + 4); var value = new byte[4]; Array.Copy(header, fieldoffset + 8, value, 0, 4); // Fields containing offsets to other IFDs if (currentifd == IFD.Zeroth && tag == 0x8769) { var exififdpointer = (int)conv.ToUInt32(value, 0); ifdqueue.Add(exififdpointer, IFD.EXIF); } else if (currentifd == IFD.Zeroth && tag == 0x8825) { var gpsifdpointer = (int)conv.ToUInt32(value, 0); ifdqueue.Add(gpsifdpointer, IFD.GPS); } else if (currentifd == IFD.EXIF && tag == 0xa005) { var 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; } var totallength = count * baselength; // If field value does not fit in 4 bytes // the value field is an offset to the actual // field value var 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 = conv.ToUInt16(value, 0); } else { thumboffset = (int)conv.ToUInt32(value, 0); } } else if (currentifd == IFD.First && tag == 0x117) { thumblength = 0; for (var j = 0; j < count; j++) { if (type == 3) { thumblength += 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 var 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 (var ts = new MemoryStream(header, tiffoffset + thumboffset, thumblength)) { Thumbnail = FromStream(ts); } } } } }
/// <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 if (header.Length - (tiffoffset + 4) >= 4) { 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); if (ifdoffset > header.Length - 1 || ifdoffset + 2 > header.Length) { Errors.Add(new ImageError(Severity.Warning, $"IFD field count overflow for IFD {currentifd}.")); continue; } 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 == 6 || type == 7) { baselength = 1; } else if (type == 3 || type == 8) { baselength = 2; } else if (type == 4 || type == 9) { baselength = 4; } else if (type == 5 || type == 10) { baselength = 8; } else // Unknown or invalid type { continue; // Skip and keep going } int totallength = (int)(count * baselength); if (totallength < 0) { Errors.Add(new ImageError(Severity.Warning, $"Field length overflow for tag {tag}.")); continue; } // 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); if (fieldposition < 0) { Errors.Add(new ImageError(Severity.Warning, $"Field offset overflow for tag {tag}.")); continue; } else if (fieldposition > header.Length - 1) { Errors.Add(new ImageError(Severity.Warning, $"Field offset for tag {tag} exceeds header length.")); continue; } else if (fieldposition + totallength > header.Length) { Errors.Add(new ImageError(Severity.Warning, $"Field length for tag {tag} exceeds header length.")); continue; } else if (totallength > int.MaxValue) { Errors.Add(new ImageError(Severity.Warning, $"Field length for tag {tag} exceeds maximum allowed length.")); continue; } 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 if (currentifd == IFD.Zeroth) { int firstifdoffset = ifdoffset + 2 + 12 * fieldcount; if (firstifdoffset + 4 <= header.Length) { int firstifdpointer = (int)conv.ToUInt32(header, firstifdoffset); if (firstifdpointer != 0) { if (firstifdpointer + 2 <= header.Length) { ifdqueue.Add(firstifdpointer, IFD.First); } else { Errors.Add(new ImageError(Severity.Warning, $"Invalid first IFD pointer.")); } } } else { Errors.Add(new ImageError(Severity.Warning, $"Invalid first IFD offset.")); } } // Read thumbnail if (thumboffset != -1 && thumblength != 0 && Thumbnail == null) { if (thumbtype == 0) { // Ensure that the thumbnail length does not exceed header length if (thumblength > header.Length - tiffoffset - thumboffset) { Errors.Add(new ImageError(Severity.Warning, $"Thumbnail size exceeds header length.")); Thumbnail = null; } else { Thumbnail = new byte[thumblength]; Array.Copy(header, tiffoffset + thumboffset, Thumbnail, 0, thumblength); } } } } }
/// <summary> /// Returns the ExifTag corresponding to the given tag id. /// </summary> public static ExifTag GetExifTag(IFD ifd, ushort tagid) { return (ExifTag)(ifd + tagid); }
/// <summary> /// Reads the APP1 section containing Exif metadata. /// </summary> private void ReadAPP1() { // Find the APP1 section containing Exif metadata app1 = file.Sections.Find(a => (a.Marker == JPEGMarker.APP1) && (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 (app1 == null) { int insertionIndex = file.Sections.FindLastIndex(a => a.Marker == JPEGMarker.APP0); if (insertionIndex == -1) { insertionIndex = 0; } insertionIndex++; ByteOrder = BitConverterEx.ByteOrder.BigEndian; app1 = new JPEGSection(JPEGMarker.APP1); file.Sections.Insert(insertionIndex, app1); return; } //#if DEBUG // mMap = new MemoryBinStream(); // mMap.Seek(0, SeekOrigin.Begin); // mMap.Write(new Bin("Exif Marker", 3, 6)); //#endif byte[] header = app1.Header; SortedList <int, IFD> ifdqueue = new SortedList <int, IFD>(); makerNoteOffset = 0; // TIFF header int tiffoffset = 6; if (header[tiffoffset] == 0x49) { ByteOrder = BitConverterEx.ByteOrder.LittleEndian; } else { ByteOrder = BitConverterEx.ByteOrder.BigEndian; } BitConverterEx conv = new BitConverterEx(ByteOrder, BitConverterEx.ByteOrder.System); // Offset to 0th IFD int ifd0offset = (int)conv.ToUInt32(header, tiffoffset + 4); ifdqueue.Add(ifd0offset, IFD.Zeroth); int thumboffset = -1; int thumblength = 0; int thumbtype = -1; #if DEBUG mMap.Write(new Bin("Byte Order: " + ByteOrder.ToString(), 4, 2)); mMap.Write(new Bin("TIFF ID: 0x00, 0x2A", 4, 2)); mMap.Write(new Bin("Offset to 0th IFD: " + ifd0offset.ToString(), 4, 4)); #endif // 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); #if DEBUG mMap.Seek(ifdoffset, SeekOrigin.Begin); mMap.Write(new Bin(currentifd.ToString() + " IFD Field Count: " + fieldcount.ToString(), 5, 2)); #endif 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); Properties.Add(prop.Tag, prop); #if DEBUG mMap.Seek(fieldoffset, SeekOrigin.Begin); mMap.Write(new Bin(ExifTagFactory.GetTagName(currentifd, tag) + " ID: " + tag.ToString(), 6, 2, prop)); mMap.Write(new Bin(ExifTagFactory.GetTagName(currentifd, tag) + " Type: " + type.ToString(), 6, 2, prop)); mMap.Write(new Bin(ExifTagFactory.GetTagName(currentifd, tag) + " Count: " + count.ToString(), 6, 4, prop)); mMap.Write(new Bin(ExifTagFactory.GetTagName(currentifd, tag) + " Value: " + string.Format("[0x{0:x2}, 0x{1:x2}, 0x{2:x2}, 0x{3:x2}]", value[0], value[1], value[2], value[3]), 6, 4, prop)); if (totallength > 4) { mMap.Seek(fieldposition, SeekOrigin.Begin); mMap.Write(new Bin(ExifTagFactory.GetTagName(currentifd, tag) + " Data", 7, totallength, prop)); } #endif } // 1st IFD pointer int firstifdpointer = (int)conv.ToUInt32(header, ifdoffset + 2 + 12 * fieldcount); if (firstifdpointer != 0) { ifdqueue.Add(firstifdpointer, IFD.First); } #if DEBUG mMap.Seek(ifdoffset + 2 + 12 * fieldcount, SeekOrigin.Begin); mMap.Write(new Bin("1st IFD Pointer: " + firstifdpointer.ToString(), 5, 4)); #endif // Read thumbnail if (thumboffset != -1 && thumblength != 0 && Thumbnail == null) { if (thumbtype == 0) { using (MemoryStream ts = new MemoryStream(header, tiffoffset + thumboffset, thumblength)) { Thumbnail = new JPEGFile(ts); } } #if DEBUG mMap.Seek(tiffoffset + thumboffset, SeekOrigin.Begin); mMap.Write(new Bin("Thumbnail", 8, thumblength)); #endif } } }
/// <summary> /// Creates an ExifProperty from the given interoperability parameters. /// </summary> /// <param name="tag">The tag id of the exif property.</param> /// <param name="type">The type id of the exif property.</param> /// <param name="count">Byte or component count.</param> /// <param name="value">Field data as an array of bytes.</param> /// <param name="byteOrder">Byte order of value.</param> /// <param name="ifd">IFD section containing this propery.</param> /// <returns>an ExifProperty initialized from the interoperability parameters.</returns> public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, BitConverterEx.ByteOrder byteOrder, IFD ifd) { BitConverterEx conv = new BitConverterEx(byteOrder, BitConverterEx.ByteOrder.System); if (ifd == IFD.Zeroth) { if (tag == 0x103) // Compression return new ExifEnumProperty<Compression>(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0)); else if (tag == 0x106) // PhotometricInterpretation return new ExifEnumProperty<PhotometricInterpretation>(ExifTag.PhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); else if (tag == 0x112) // Orientation return new ExifEnumProperty<Orientation>(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0)); else if (tag == 0x11c) // PlanarConfiguration return new ExifEnumProperty<PlanarConfiguration>(ExifTag.PlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); else if (tag == 0x213) // YCbCrPositioning return new ExifEnumProperty<YCbCrPositioning>(ExifTag.YCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); else if (tag == 0x128) // ResolutionUnit return new ExifEnumProperty<ResolutionUnit>(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); else if (tag == 0x132) // DateTime return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); } else if (ifd == IFD.EXIF) { if (tag == 0x9000) // ExifVersion return new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value)); else if (tag == 0xa000) // FlashpixVersion return new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value)); else if (tag == 0xa001) // ColorSpace return new ExifEnumProperty<ColorSpace>(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0)); else if (tag == 0x9286) // UserComment { byte[] encbytes = new byte[8]; byte[] strbytes = new byte[value.Length - 8]; Array.Copy(value, encbytes, 8); Array.Copy(value, 8, strbytes, 0, value.Length - 8); Encoding enc = Encoding.UTF8; string encstr = enc.GetString(encbytes, 0, encbytes.Length); if (encstr == "ASCII\0\0\0") enc = Encoding.UTF8; else if (encstr == "JIS\0\0\0\0\0") enc = Encoding.GetEncoding("Japanese (JIS 0208-1990 and 0212-1990)"); else if (encstr == "Unicode\0") enc = Encoding.Unicode; else enc = null; int len = Array.IndexOf(strbytes, (byte)0); if (len == -1) len = strbytes.Length; return new ExifEncodedString(ExifTag.UserComment, (enc == null ? Encoding.UTF8.GetString(strbytes, 0, len) : enc.GetString(strbytes, 0, len)), enc); } else if (tag == 0x9003) // DateTimeOriginal return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value)); else if (tag == 0x9004) // DateTimeDigitized return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value)); else if (tag == 0x8822) // ExposureProgram return new ExifEnumProperty<ExposureProgram>(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0)); else if (tag == 0x9207) // MeteringMode return new ExifEnumProperty<MeteringMode>(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0)); else if (tag == 0x9208) // LightSource return new ExifEnumProperty<LightSource>(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0)); else if (tag == 0x9209) // Flash return new ExifEnumProperty<Flash>(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true); else if (tag == 0x9214) // SubjectArea { if (count == 3) return new ExifCircularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); else if (count == 4) return new ExifRectangularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); else // count == 2 return new ExifPointSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } else if (tag == 0xa210) // FocalPlaneResolutionUnit return new ExifEnumProperty<ResolutionUnit>(ExifTag.FocalPlaneResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0), true); else if (tag == 0xa214) // SubjectLocation return new ExifPointSubjectArea(ExifTag.SubjectLocation, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); else if (tag == 0xa217) // SensingMethod return new ExifEnumProperty<SensingMethod>(ExifTag.SensingMethod, (SensingMethod)conv.ToUInt16(value, 0), true); else if (tag == 0xa300) // FileSource return new ExifEnumProperty<FileSource>(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true); else if (tag == 0xa301) // SceneType return new ExifEnumProperty<SceneType>(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true); else if (tag == 0xa401) // CustomRendered return new ExifEnumProperty<CustomRendered>(ExifTag.CustomRendered, (CustomRendered)conv.ToUInt16(value, 0), true); else if (tag == 0xa402) // ExposureMode return new ExifEnumProperty<ExposureMode>(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), true); else if (tag == 0xa403) // WhiteBalance return new ExifEnumProperty<WhiteBalance>(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), true); else if (tag == 0xa406) // SceneCaptureType return new ExifEnumProperty<SceneCaptureType>(ExifTag.SceneCaptureType, (SceneCaptureType)conv.ToUInt16(value, 0), true); else if (tag == 0xa407) // GainControl return new ExifEnumProperty<GainControl>(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), true); else if (tag == 0xa408) // Contrast return new ExifEnumProperty<Contrast>(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true); else if (tag == 0xa409) // Saturation return new ExifEnumProperty<Saturation>(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true); else if (tag == 0xa40a) // Sharpness return new ExifEnumProperty<Sharpness>(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true); else if (tag == 0xa40c) // SubjectDistanceRange return new ExifEnumProperty<SubjectDistanceRange>(ExifTag.SubjectDistance, (SubjectDistanceRange)conv.ToUInt16(value, 0), true); } else if (ifd == IFD.GPS) { if (tag == 0) // GPSVersionID return new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value)); else if (tag == 1) // GPSLatitudeRef return new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0]); else if (tag == 2) // GPSLatitude return new GPSLatitudeLongitude(ExifTag.GPSLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 3) // GPSLongitudeRef return new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0]); else if (tag == 4) // GPSLongitude return new GPSLatitudeLongitude(ExifTag.GPSLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 5) // GPSAltitudeRef return new ExifEnumProperty<GPSAltitudeRef>(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0]); else if (tag == 7) // GPSTimeStamp return new GPSTimeStamp(ExifTag.GPSTimeStamp, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 9) // GPSStatus return new ExifEnumProperty<GPSStatus>(ExifTag.GPSStatus, (GPSStatus)value[0]); else if (tag == 10) // GPSMeasureMode return new ExifEnumProperty<GPSMeasureMode>(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0]); else if (tag == 12) // GPSSpeedRef return new ExifEnumProperty<GPSSpeedRef>(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0]); else if (tag == 14) // GPSTrackRef return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0]); else if (tag == 16) // GPSImgDirectionRef return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0]); else if (tag == 19) // GPSDestLatitudeRef return new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0]); else if (tag == 20) // GPSDestLatitude return new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 21) // GPSDestLongitudeRef return new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0]); else if (tag == 22) // GPSDestLongitude return new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 23) // GPSDestBearingRef return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0]); else if (tag == 25) // GPSDestDistanceRef return new ExifEnumProperty<GPSDistanceRef>(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]); else if (tag == 29) // GPSDate return new ExifDateTime(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false)); else if (tag == 30) // GPSDifferential return new ExifEnumProperty<GPSDifferential>(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0)); } else if (ifd == IFD.Interop) { if (tag == 1) // InteroperabilityIndex return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value)); else if (tag == 2) // InteroperabilityVersion return new ExifVersion(ExifTag.InteroperabilityVersion, ExifBitConverter.ToAscii(value)); } else if (ifd == IFD.First) { if (tag == 0x103) // Compression return new ExifEnumProperty<Compression>(ExifTag.ThumbnailCompression, (Compression)conv.ToUInt16(value, 0)); else if (tag == 0x106) // PhotometricInterpretation return new ExifEnumProperty<PhotometricInterpretation>(ExifTag.ThumbnailPhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); else if (tag == 0x112) // Orientation return new ExifEnumProperty<Orientation>(ExifTag.ThumbnailOrientation, (Orientation)conv.ToUInt16(value, 0)); else if (tag == 0x11c) // PlanarConfiguration return new ExifEnumProperty<PlanarConfiguration>(ExifTag.ThumbnailPlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); else if (tag == 0x213) // YCbCrPositioning return new ExifEnumProperty<YCbCrPositioning>(ExifTag.ThumbnailYCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); else if (tag == 0x128) // ResolutionUnit return new ExifEnumProperty<ResolutionUnit>(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); else if (tag == 0x132) // DateTime return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value)); } // Find the exif tag corresponding to given tag id ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag); if (type == 1) // 1 = BYTE An 8-bit unsigned integer. { if (count == 1) return new ExifByte(etag, value[0]); else return new ExifByteArray(etag, value); } else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. { return new ExifAscii(etag, ExifBitConverter.ToAscii(value)); } else if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer. { if (count == 1) return new ExifUShort(etag, conv.ToUInt16(value, 0)); else return new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } else if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer. { if (count == 1) return new ExifUInt(etag, conv.ToUInt32(value, 0)); else return new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder)); } else if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. { if (count == 1) return new ExifURational(etag, ExifBitConverter.ToURational(value, byteOrder)); else return new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } else if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. return new ExifUndefined(etag, value); else if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). { if (count == 1) return new ExifSInt(etag, conv.ToInt32(value, 0)); else return new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder)); } else if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. { if (count == 1) return new ExifSRational(etag, ExifBitConverter.ToSRational(value, byteOrder)); else return new ExifSRationalArray(etag, ExifBitConverter.ToSRationalArray(value, (int)count, byteOrder)); } else throw new ArgumentException("Unknown property type."); }
private void WriteIFD(MemoryStream stream, Dictionary<ExifTag, ExifProperty> ifd, IFD ifdtype, long tiffoffset, bool preserveMakerNote) { BitConverterEx conv = new BitConverterEx(BitConverterEx.ByteOrder.System, 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 makernote 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) 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) { if (interop.TypeID == 1 || interop.TypeID == 3 || interop.TypeID == 4 || interop.TypeID == 9) Array.Reverse(data); else if (interop.TypeID == 5 || interop.TypeID == 10) { Array.Reverse(data, 0, 4); Array.Reverse(data, 4, 4); } } // Fields containing offsets to other IFDs // Just store their offets, 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; } } }
void GetWB() { // Set the whitebalance for all the modern ARW formats (everything after A100) Tag priv = ifd.GetEntryRecursive(TagType.DNGPRIVATEDATA); if (priv != null) { byte[] data = priv.GetByteArray(); uint off = ((((uint)(data)[3]) << 24) | (((uint)(data)[2]) << 16) | (((uint)(data)[1]) << 8) | (data)[0]); IFD sony_private; sony_private = new IFD(reader, off, ifd.endian, ifd.Depth); Tag sony_offset = sony_private.GetEntryRecursive(TagType.SONY_OFFSET); Tag sony_length = sony_private.GetEntryRecursive(TagType.SONY_LENGTH); Tag sony_key = sony_private.GetEntryRecursive(TagType.SONY_KEY); if (sony_offset == null || sony_length == null || sony_key == null || sony_key.dataCount != 4) { throw new RawDecoderException("Couldn't find the correct metadata for white balance decoding"); } off = sony_offset.GetUInt(0); uint len = sony_length.GetUInt(0); data = sony_key.GetByteArray(); uint key = (uint)((data[3] << 24) | (data[2] << 16) | (data[1] << 8) | (data)[0]); reader.BaseStream.Position = off; byte[] ifp_data = reader.ReadBytes((int)len); SonyDecrypt(ifp_data, len / 4, key); using (var reader = new ImageBinaryReader(ifp_data)) { sony_private = new IFD(reader, 0, ifd.endian, 0, -(int)off); } Tag wb = sony_private.GetEntry(TagType.SONYGRBGLEVELS); if (wb != null) { if (wb.dataCount != 4) { throw new RawDecoderException("White balance has " + wb.dataCount + " entries instead of 4"); } rawImage.metadata.WbCoeffs = new WhiteBalance(wb.GetInt(1), wb.GetInt(0), wb.GetInt(2), rawImage.fullSize.ColorDepth); } else if ((wb = sony_private.GetEntry(TagType.SONYRGGBLEVELS)) != null) { if (wb.dataCount != 4) { throw new RawDecoderException("White balance has " + wb.dataCount + " entries instead of 4"); } rawImage.metadata.WbCoeffs = new WhiteBalance(wb.GetInt(0), wb.GetInt(1), wb.GetInt(3), rawImage.fullSize.ColorDepth); } //TODO read the color matrix 0x7800 Tag black = sony_private.GetEntry((TagType)0x7300) ?? sony_private.GetEntry((TagType)0x7310); if (black != null) { rawImage.black = black.GetLong(0); } Tag white = sony_private.GetEntry((TagType)0x787f); if (white != null) { rawImage.whitePoint = white.GetLong(0); } } }
public IFDAttribute(IFD ifd) { IFD = ifd; }
/// <summary> /// Creates an ExifProperty from the given interoperability parameters. /// </summary> /// <param name="tag">The tag id of the exif property.</param> /// <param name="type">The type id of the exif property.</param> /// <param name="count">Byte or component count.</param> /// <param name="value">Field data as an array of bytes.</param> /// <param name="byteOrder">Byte order of value.</param> /// <param name="ifd">IFD section containing this propery.</param> /// <param name="encoding">The encoding to be used for text metadata when the source encoding is unknown.</param> /// <returns>an ExifProperty initialized from the interoperability parameters.</returns> public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, BitConverterEx.ByteOrder byteOrder, IFD ifd, Encoding encoding) { BitConverterEx conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); // Find the exif tag corresponding to given tag id ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag); if (ifd == IFD.Zeroth) { if (tag == 0x103) // Compression return new ExifEnumProperty<Compression>(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0)); else if (tag == 0x106) // PhotometricInterpretation return new ExifEnumProperty<PhotometricInterpretation>(ExifTag.PhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); else if (tag == 0x112) // Orientation return new ExifEnumProperty<Orientation>(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0)); else if (tag == 0x11c) // PlanarConfiguration return new ExifEnumProperty<PlanarConfiguration>(ExifTag.PlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); else if (tag == 0x213) // YCbCrPositioning return new ExifEnumProperty<YCbCrPositioning>(ExifTag.YCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); else if (tag == 0x128) // ResolutionUnit return new ExifEnumProperty<ResolutionUnit>(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); else if (tag == 0x132) // DateTime return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd('\0')); } else if (ifd == IFD.EXIF) { if (tag == 0x9000) // ExifVersion return new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); else if (tag == 0xa000) // FlashpixVersion return new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); else if (tag == 0xa001) // ColorSpace return new ExifEnumProperty<ColorSpace>(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0)); else if (tag == 0x9286) // UserComment { // Default to ASCII Encoding enc = Encoding.ASCII; bool hasenc; if (value.Length < 8) hasenc = false; else { hasenc = true; string encstr = enc.GetString(value, 0, 8); if (string.Compare(encstr, "ASCII\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) enc = Encoding.ASCII; else if (string.Compare(encstr, "JIS\0\0\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) enc = Encoding.GetEncoding("Japanese (JIS 0208-1990 and 0212-1990)"); else if (string.Compare(encstr, "Unicode\0", StringComparison.OrdinalIgnoreCase) == 0) enc = Encoding.Unicode; else hasenc = false; } string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim('\0'); return new ExifEncodedString(ExifTag.UserComment, val, enc); } else if (tag == 0x9003) // DateTimeOriginal return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value)); else if (tag == 0x9004) // DateTimeDigitized return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value)); else if (tag == 0x8822) // ExposureProgram return new ExifEnumProperty<ExposureProgram>(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0)); else if (tag == 0x9207) // MeteringMode return new ExifEnumProperty<MeteringMode>(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0)); else if (tag == 0x9208) // LightSource return new ExifEnumProperty<LightSource>(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0)); else if (tag == 0x9209) // Flash return new ExifEnumProperty<Flash>(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true); else if (tag == 0x9214) // SubjectArea { if (count == 3) return new ExifCircularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); else if (count == 4) return new ExifRectangularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); else // count == 2 return new ExifPointSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } else if (tag == 0xa210) // FocalPlaneResolutionUnit return new ExifEnumProperty<ResolutionUnit>(ExifTag.FocalPlaneResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0), true); else if (tag == 0xa214) // SubjectLocation return new ExifPointSubjectArea(ExifTag.SubjectLocation, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); else if (tag == 0xa217) // SensingMethod return new ExifEnumProperty<SensingMethod>(ExifTag.SensingMethod, (SensingMethod)conv.ToUInt16(value, 0), true); else if (tag == 0xa300) // FileSource return new ExifEnumProperty<FileSource>(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true); else if (tag == 0xa301) // SceneType return new ExifEnumProperty<SceneType>(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true); else if (tag == 0xa401) // CustomRendered return new ExifEnumProperty<CustomRendered>(ExifTag.CustomRendered, (CustomRendered)conv.ToUInt16(value, 0), true); else if (tag == 0xa402) // ExposureMode return new ExifEnumProperty<ExposureMode>(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), true); else if (tag == 0xa403) // WhiteBalance return new ExifEnumProperty<WhiteBalance>(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), true); else if (tag == 0xa406) // SceneCaptureType return new ExifEnumProperty<SceneCaptureType>(ExifTag.SceneCaptureType, (SceneCaptureType)conv.ToUInt16(value, 0), true); else if (tag == 0xa407) // GainControl return new ExifEnumProperty<GainControl>(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), true); else if (tag == 0xa408) // Contrast return new ExifEnumProperty<Contrast>(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true); else if (tag == 0xa409) // Saturation return new ExifEnumProperty<Saturation>(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true); else if (tag == 0xa40a) // Sharpness return new ExifEnumProperty<Sharpness>(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true); else if (tag == 0xa40c) // SubjectDistanceRange return new ExifEnumProperty<SubjectDistanceRange>(ExifTag.SubjectDistanceRange, (SubjectDistanceRange)conv.ToUInt16(value, 0), true); } else if (ifd == IFD.GPS) { if (tag == 0) // GPSVersionID return new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value)); else if (tag == 1) // GPSLatitudeRef return new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0]); else if (tag == 2) // GPSLatitude return new GPSLatitudeLongitude(ExifTag.GPSLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 3) // GPSLongitudeRef return new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0]); else if (tag == 4) // GPSLongitude return new GPSLatitudeLongitude(ExifTag.GPSLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 5) // GPSAltitudeRef return new ExifEnumProperty<GPSAltitudeRef>(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0]); else if (tag == 7) // GPSTimeStamp return new GPSTimeStamp(ExifTag.GPSTimeStamp, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 9) // GPSStatus return new ExifEnumProperty<GPSStatus>(ExifTag.GPSStatus, (GPSStatus)value[0]); else if (tag == 10) // GPSMeasureMode return new ExifEnumProperty<GPSMeasureMode>(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0]); else if (tag == 12) // GPSSpeedRef return new ExifEnumProperty<GPSSpeedRef>(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0]); else if (tag == 14) // GPSTrackRef return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0]); else if (tag == 16) // GPSImgDirectionRef return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0]); else if (tag == 19) // GPSDestLatitudeRef return new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0]); else if (tag == 20) // GPSDestLatitude return new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 21) // GPSDestLongitudeRef return new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0]); else if (tag == 22) // GPSDestLongitude return new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); else if (tag == 23) // GPSDestBearingRef return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0]); else if (tag == 25) // GPSDestDistanceRef return new ExifEnumProperty<GPSDistanceRef>(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]); else if (tag == 29) // GPSDate return new ExifDateTime(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false)); else if (tag == 30) // GPSDifferential return new ExifEnumProperty<GPSDifferential>(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0)); } else if (ifd == IFD.Interop) { if (tag == 1) // InteroperabilityIndex return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value, Encoding.ASCII), Encoding.ASCII); else if (tag == 2) // InteroperabilityVersion return new ExifVersion(ExifTag.InteroperabilityVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); } else if (ifd == IFD.First) { if (tag == 0x103) // Compression return new ExifEnumProperty<Compression>(ExifTag.ThumbnailCompression, (Compression)conv.ToUInt16(value, 0)); else if (tag == 0x106) // PhotometricInterpretation return new ExifEnumProperty<PhotometricInterpretation>(ExifTag.ThumbnailPhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); else if (tag == 0x112) // Orientation return new ExifEnumProperty<Orientation>(ExifTag.ThumbnailOrientation, (Orientation)conv.ToUInt16(value, 0)); else if (tag == 0x11c) // PlanarConfiguration return new ExifEnumProperty<PlanarConfiguration>(ExifTag.ThumbnailPlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); else if (tag == 0x213) // YCbCrPositioning return new ExifEnumProperty<YCbCrPositioning>(ExifTag.ThumbnailYCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); else if (tag == 0x128) // ResolutionUnit return new ExifEnumProperty<ResolutionUnit>(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); else if (tag == 0x132) // DateTime return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value)); } if (type == 1) // 1 = BYTE An 8-bit unsigned integer. { if (count == 1) return new ExifByte(etag, value[0]); else return new ExifByteArray(etag, value); } else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. { return new ExifAscii(etag, ExifBitConverter.ToAscii(value, encoding), encoding); } else if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer. { if (count == 1) return new ExifUShort(etag, conv.ToUInt16(value, 0)); else return new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } else if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer. { if (count == 1) return new ExifUInt(etag, conv.ToUInt32(value, 0)); else return new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder)); } else if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. { if (count == 1) return new ExifURational(etag, ExifBitConverter.ToURational(value, byteOrder)); else return new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } else if (type == 6) // 6 = SBYTE An 8-bit signed (twos-complement) integer. { if (count == 1) return new ExifSByte(etag, Convert.ToSByte(value[0])); else return new ExifSByteArray(etag, Array.ConvertAll(value, b => Convert.ToSByte(b))); } else if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. { return new ExifUndefined(etag, value); } else if (type == 8) // 8 = SSHORT A 16-bit (2-byte) signed short { if (count == 1) return new ExifSShort(etag, conv.ToInt16(value, 0)); else return new ExifSShortArray(etag, ExifBitConverter.ToSShortArray(value, (int)count, byteOrder)); } else if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). { if (count == 1) return new ExifSInt(etag, conv.ToInt32(value, 0)); else return new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder)); } else if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. { if (count == 1) return new ExifSRational(etag, ExifBitConverter.ToSRational(value, byteOrder)); else return new ExifSRationalArray(etag, ExifBitConverter.ToSRationalArray(value, (int)count, byteOrder)); } else if (type == 11) // 11 = FLOAT A single precision (4-byte) IEEE format { if (count == 1) return new ExifFloat(etag, conv.ToSingle(value, 0)); else return new ExifFloatArray(etag, ExifBitConverter.ToSingleArray(value, (int)count, byteOrder)); } else if (type == 12) // 12 = DOUBLE A double precision (8-byte) IEEE format { if (count == 1) return new ExifDouble(etag, conv.ToDouble(value, 0)); else return new ExifDoubleArray(etag, ExifBitConverter.ToDoubleArray(value, (int)count, byteOrder)); } else throw new ArgumentException("Unknown property type."); }
/// <summary> /// Creates an ExifProperty from the given interoperability parameters. /// </summary> /// <param name="tag">The tag id of the exif property.</param> /// <param name="type">The type id of the exif property.</param> /// <param name="count">Byte or component count.</param> /// <param name="value">Field data as an array of bytes.</param> /// <param name="byteOrder">Byte order of value.</param> /// <param name="ifd">IFD section containing this propery.</param> /// <param name="encoding">The encoding to be used for text metadata when the source encoding is unknown.</param> /// <returns>an ExifProperty initialized from the interoperability parameters.</returns> public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, BitConverterEx.ByteOrder byteOrder, IFD ifd, Encoding encoding) { BitConverterEx conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); // Find the exif tag corresponding to given tag id ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag); if (ifd == IFD.Zeroth) { if (tag == 0x103) // Compression { return(new ExifEnumProperty <Compression>(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0))); } else if (tag == 0x106) // PhotometricInterpretation { return(new ExifEnumProperty <PhotometricInterpretation>(ExifTag.PhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0))); } else if (tag == 0x112) // Orientation { return(new ExifEnumProperty <Orientation>(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0))); } else if (tag == 0x11c) // PlanarConfiguration { return(new ExifEnumProperty <PlanarConfiguration>(ExifTag.PlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0))); } else if (tag == 0x213) // YCbCrPositioning { return(new ExifEnumProperty <YCbCrPositioning>(ExifTag.YCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0))); } else if (tag == 0x128) // ResolutionUnit { return(new ExifEnumProperty <ResolutionUnit>(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0))); } else if (tag == 0x132) // DateTime { return(new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value))); } else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) { return(new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd('\0'))); } } else if (ifd == IFD.EXIF) { if (tag == 0x9000) // ExifVersion { return(new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII))); } else if (tag == 0xa000) // FlashpixVersion { return(new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII))); } else if (tag == 0xa001) // ColorSpace { return(new ExifEnumProperty <ColorSpace>(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0))); } else if (tag == 0x9286) // UserComment { // Default to ASCII Encoding enc = Encoding.ASCII; bool hasenc; if (value.Length < 8) { hasenc = false; } else { hasenc = true; string encstr = enc.GetString(value, 0, 8); if (string.Compare(encstr, "ASCII\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) { enc = Encoding.ASCII; } else if (string.Compare(encstr, "JIS\0\0\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) { enc = Encoding.GetEncoding("Japanese (JIS 0208-1990 and 0212-1990)"); } else if (string.Compare(encstr, "Unicode\0", StringComparison.OrdinalIgnoreCase) == 0) { enc = Encoding.Unicode; } else { hasenc = false; } } string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim('\0'); return(new ExifEncodedString(ExifTag.UserComment, val, enc)); } else if (tag == 0x9003) // DateTimeOriginal { return(new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value))); } else if (tag == 0x9004) // DateTimeDigitized { return(new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value))); } else if (tag == 0x8822) // ExposureProgram { return(new ExifEnumProperty <ExposureProgram>(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0))); } else if (tag == 0x9207) // MeteringMode { return(new ExifEnumProperty <MeteringMode>(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0))); } else if (tag == 0x9208) // LightSource { return(new ExifEnumProperty <LightSource>(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0))); } else if (tag == 0x9209) // Flash { return(new ExifEnumProperty <Flash>(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true)); } else if (tag == 0x9214) // SubjectArea { if (count == 3) { return(new ExifCircularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder))); } else if (count == 4) { return(new ExifRectangularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder))); } else // count == 2 { return(new ExifPointSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder))); } } else if (tag == 0xa210) // FocalPlaneResolutionUnit { return(new ExifEnumProperty <ResolutionUnit>(ExifTag.FocalPlaneResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa214) // SubjectLocation { return(new ExifPointSubjectArea(ExifTag.SubjectLocation, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder))); } else if (tag == 0xa217) // SensingMethod { return(new ExifEnumProperty <SensingMethod>(ExifTag.SensingMethod, (SensingMethod)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa300) // FileSource { return(new ExifEnumProperty <FileSource>(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa301) // SceneType { return(new ExifEnumProperty <SceneType>(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa401) // CustomRendered { return(new ExifEnumProperty <CustomRendered>(ExifTag.CustomRendered, (CustomRendered)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa402) // ExposureMode { return(new ExifEnumProperty <ExposureMode>(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa403) // WhiteBalance { return(new ExifEnumProperty <WhiteBalance>(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa406) // SceneCaptureType { return(new ExifEnumProperty <SceneCaptureType>(ExifTag.SceneCaptureType, (SceneCaptureType)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa407) // GainControl { return(new ExifEnumProperty <GainControl>(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa408) // Contrast { return(new ExifEnumProperty <Contrast>(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa409) // Saturation { return(new ExifEnumProperty <Saturation>(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa40a) // Sharpness { return(new ExifEnumProperty <Sharpness>(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa40c) // SubjectDistanceRange { return(new ExifEnumProperty <SubjectDistanceRange>(ExifTag.SubjectDistanceRange, (SubjectDistanceRange)conv.ToUInt16(value, 0), true)); } else if (tag == 0xa432) // LensSpecification { return(new LensSpecification(ExifTag.LensSpecification, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } } else if (ifd == IFD.GPS) { if (tag == 0) // GPSVersionID { return(new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value))); } else if (tag == 1) // GPSLatitudeRef { return(new ExifEnumProperty <GPSLatitudeRef>(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0])); } else if (tag == 2) // GPSLatitude { return(new GPSLatitudeLongitude(ExifTag.GPSLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } else if (tag == 3) // GPSLongitudeRef { return(new ExifEnumProperty <GPSLongitudeRef>(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0])); } else if (tag == 4) // GPSLongitude { return(new GPSLatitudeLongitude(ExifTag.GPSLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } else if (tag == 5) // GPSAltitudeRef { return(new ExifEnumProperty <GPSAltitudeRef>(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0])); } else if (tag == 7) // GPSTimeStamp { return(new GPSTimeStamp(ExifTag.GPSTimeStamp, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } else if (tag == 9) // GPSStatus { return(new ExifEnumProperty <GPSStatus>(ExifTag.GPSStatus, (GPSStatus)value[0])); } else if (tag == 10) // GPSMeasureMode { return(new ExifEnumProperty <GPSMeasureMode>(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0])); } else if (tag == 12) // GPSSpeedRef { return(new ExifEnumProperty <GPSSpeedRef>(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0])); } else if (tag == 14) // GPSTrackRef { return(new ExifEnumProperty <GPSDirectionRef>(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0])); } else if (tag == 16) // GPSImgDirectionRef { return(new ExifEnumProperty <GPSDirectionRef>(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0])); } else if (tag == 19) // GPSDestLatitudeRef { return(new ExifEnumProperty <GPSLatitudeRef>(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0])); } else if (tag == 20) // GPSDestLatitude { return(new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } else if (tag == 21) // GPSDestLongitudeRef { return(new ExifEnumProperty <GPSLongitudeRef>(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0])); } else if (tag == 22) // GPSDestLongitude { return(new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } else if (tag == 23) // GPSDestBearingRef { return(new ExifEnumProperty <GPSDirectionRef>(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0])); } else if (tag == 25) // GPSDestDistanceRef { return(new ExifEnumProperty <GPSDistanceRef>(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0])); } else if (tag == 29) // GPSDateStamp { return(new ExifDate(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false))); } else if (tag == 30) // GPSDifferential { return(new ExifEnumProperty <GPSDifferential>(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0))); } } else if (ifd == IFD.Interop) { if (tag == 1) // InteroperabilityIndex { return(new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value, Encoding.ASCII), Encoding.ASCII)); } else if (tag == 2) // InteroperabilityVersion { return(new ExifVersion(ExifTag.InteroperabilityVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII))); } } else if (ifd == IFD.First) { if (tag == 0x103) // Compression { return(new ExifEnumProperty <Compression>(ExifTag.ThumbnailCompression, (Compression)conv.ToUInt16(value, 0))); } else if (tag == 0x106) // PhotometricInterpretation { return(new ExifEnumProperty <PhotometricInterpretation>(ExifTag.ThumbnailPhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0))); } else if (tag == 0x112) // Orientation { return(new ExifEnumProperty <Orientation>(ExifTag.ThumbnailOrientation, (Orientation)conv.ToUInt16(value, 0))); } else if (tag == 0x11c) // PlanarConfiguration { return(new ExifEnumProperty <PlanarConfiguration>(ExifTag.ThumbnailPlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0))); } else if (tag == 0x213) // YCbCrPositioning { return(new ExifEnumProperty <YCbCrPositioning>(ExifTag.ThumbnailYCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0))); } else if (tag == 0x128) // ResolutionUnit { return(new ExifEnumProperty <ResolutionUnit>(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0))); } else if (tag == 0x132) // DateTime { return(new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value))); } } if (type == 1) // 1 = BYTE An 8-bit unsigned integer. { if (count == 1) { return(new ExifByte(etag, value[0])); } else { return(new ExifByteArray(etag, value)); } } else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. { return(new ExifAscii(etag, ExifBitConverter.ToAscii(value, encoding), encoding)); } else if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer. { if (count == 1) { return(new ExifUShort(etag, conv.ToUInt16(value, 0))); } else { return(new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder))); } } else if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer. { if (count == 1) { return(new ExifUInt(etag, conv.ToUInt32(value, 0))); } else { return(new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder))); } } else if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. { if (count == 1) { return(new ExifURational(etag, ExifBitConverter.ToURational(value, byteOrder))); } else { return(new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder))); } } else if (type == 6) // 1 = SBYTE An 8-bit signed integer. { if (count == 1) { return(new ExifSByte(etag, (sbyte)value[0])); } else { sbyte[] data = new sbyte[count]; Buffer.BlockCopy(value, 0, data, 0, (int)count); return(new ExifSByteArray(etag, data)); } } else if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. { return(new ExifUndefined(etag, value)); } else if (type == 8) // 8 = SSHORT A 16-bit (2-byte) signed integer. { if (count == 1) { return(new ExifSShort(etag, conv.ToInt16(value, 0))); } else { return(new ExifSShortArray(etag, ExifBitConverter.ToSShortArray(value, (int)count, byteOrder))); } } else if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). { if (count == 1) { return(new ExifSInt(etag, conv.ToInt32(value, 0))); } else { return(new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder))); } } else if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. { if (count == 1) { return(new ExifSRational(etag, ExifBitConverter.ToSRational(value, byteOrder))); } else { return(new ExifSRationalArray(etag, ExifBitConverter.ToSRationalArray(value, (int)count, byteOrder))); } } else if (type == 11) // 11 = FLOAT Single precision (4-byte) IEEE format. { if (count == 1) { return(new ExifFloat(etag, conv.ToSingle(value, 0))); } else { return(new ExifFloatArray(etag, ExifBitConverter.ToSingleArray(value, (int)count, byteOrder))); } } else if (type == 12) // 12 = DOUBLE Double precision (8-byte) IEEE format. { if (count == 1) { return(new ExifDouble(etag, conv.ToDouble(value, 0))); } else { return(new ExifDoubleArray(etag, ExifBitConverter.ToDoubleArray(value, (int)count, byteOrder))); } } else { throw new ArgumentException("Unknown property type."); } }
/// <summary> /// Creates an ExifProperty from the given interoperability parameters. /// </summary> /// <param name="interOperability">Property data.</param> /// <param name="byteOrder">Byte order of the source data.</param> /// <param name="ifd">IFD section containing this propery.</param> /// <returns>an ExifProperty initialized from the interoperability parameters.</returns> public static ExifProperty Get(ExifInterOperability interOperability, BitConverterEx.ByteOrder byteOrder, IFD ifd) { return Get(interOperability.TagID, interOperability.TypeID, interOperability.Count, interOperability.Data, byteOrder, ifd); }
/// <summary> /// 返回ExifTag对应于给定的标签ID /// </summary> public static ExifTag GetExifTag(IFD ifd, ushort tagid) { return((ExifTag)(ifd + tagid)); }
/// <summary> /// 返回对应于给定ExifTag的标签ID /// </summary> public static ushort GetTagID(ExifTag exiftag) { IFD ifd = GetTagIFD(exiftag); return((ushort)((int)exiftag - (int)ifd)); }
private void WriteIFD(MemoryStream stream, Dictionary <ExifTag, ExifProperty> ifd, IFD ifdtype, long tiffoffset, bool preserveMakerNote) { BitConverterEx conv = new BitConverterEx(BitConverterEx.ByteOrder.System, 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 makernote 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) { 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) { if (interop.TypeID == 1 || interop.TypeID == 3 || interop.TypeID == 4 || interop.TypeID == 9) { Array.Reverse(data); } else if (interop.TypeID == 5 || interop.TypeID == 10) { Array.Reverse(data, 0, 4); Array.Reverse(data, 4, 4); } } // Fields containing offsets to other IFDs // Just store their offets, 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; } } }
/// <summary> /// 返回给定标签ID字符串表示 /// </summary> public static string GetTagName(IFD ifd, ushort tagid) { return(GetTagName(GetExifTag(ifd, tagid))); }