/// <summary> /// Reads the APP0 section containing JFIF extension metadata. /// </summary> private void ReadJFXXAPP0() { // Find the APP0 section containing JFIF metadata jfxxApp0 = Sections.Find(a => (a.Marker == JPEGMarker.APP0) && a.Header.Length >= 5 && (Encoding.ASCII.GetString(a.Header, 0, 5) == "JFXX\0")); // If there is no APP0 section, return. if (jfxxApp0 == null) { return; } byte[] header = jfxxApp0.Header; // Version JFIFExtension version = (JFIFExtension)header[5]; Properties.Add(new ExifEnumProperty <JFIFExtension>(ExifTag.JFXXExtensionCode, version)); // Read thumbnail if (version == JFIFExtension.ThumbnailJPEG) { byte[] data = new byte[header.Length - 6]; Array.Copy(header, 6, data, 0, data.Length); Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, data))); } else if (version == JFIFExtension.Thumbnail24BitRGB) { // Thumbnails pixel count byte xthumbnail = header[6]; Properties.Add(new ExifByte(ExifTag.JFXXXThumbnail, xthumbnail)); byte ythumbnail = header[7]; Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); byte[] data = new byte[3 * xthumbnail * ythumbnail]; Array.Copy(header, 8, data, 0, data.Length); Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.BMP24Bit, data))); } else if (version == JFIFExtension.ThumbnailPaletteRGB) { // Thumbnails pixel count byte xthumbnail = header[6]; Properties.Add(new ExifByte(ExifTag.JFXXXThumbnail, xthumbnail)); byte ythumbnail = header[7]; Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); byte[] palette = new byte[768]; Array.Copy(header, 8, palette, 0, palette.Length); byte[] data = new byte[xthumbnail * ythumbnail]; Array.Copy(header, 8 + 768, data, 0, data.Length); Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(palette, data))); } }
/// <summary> /// Reads the APP0 section containing JFIF metadata. /// </summary> private void ReadJFIFAPP0() { // Find the APP0 section containing JFIF metadata jfifApp0 = Sections.Find(a => (a.Marker == JPEGMarker.APP0) && a.Header.Length >= 5 && (Encoding.ASCII.GetString(a.Header, 0, 5) == "JFIF\0")); // If there is no APP0 section, return. if (jfifApp0 == null) { return; } byte[] header = jfifApp0.Header; BitConverterEx jfifConv = BitConverterEx.BigEndian; // Version ushort version = jfifConv.ToUInt16(header, 5); Properties.Add(new JFIFVersion(ExifTag.JFIFVersion, version)); // Units byte unit = header[7]; Properties.Add(new ExifEnumProperty <JFIFDensityUnit>(ExifTag.JFIFUnits, (JFIFDensityUnit)unit)); // X and Y densities ushort xdensity = jfifConv.ToUInt16(header, 8); Properties.Add(new ExifUShort(ExifTag.XDensity, xdensity)); ushort ydensity = jfifConv.ToUInt16(header, 10); Properties.Add(new ExifUShort(ExifTag.YDensity, ydensity)); // Thumbnails pixel count byte xthumbnail = header[12]; Properties.Add(new ExifByte(ExifTag.JFIFXThumbnail, xthumbnail)); byte ythumbnail = header[13]; Properties.Add(new ExifByte(ExifTag.JFIFYThumbnail, ythumbnail)); // Read JFIF thumbnail int n = xthumbnail * ythumbnail; byte[] jfifThumbnail = new byte[n]; Array.Copy(header, 14, jfifThumbnail, 0, n); Properties.Add(new JFIFThumbnailProperty(ExifTag.JFIFThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, jfifThumbnail))); }
/// <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); } } } } }
/// <summary> /// Initializes a new instance of the <see cref="ExifFile"/> class. /// </summary> /// <param name="stream">A <see cref="Sytem.IO.Stream"/> that contains image data.</param> /// <param name="encoding">The encoding to be used for text metadata when the source encoding is unknown.</param> protected internal JPEGFile(Stream stream, Encoding encoding) { Format = ImageFileFormat.JPEG; Sections = new List <JPEGSection>(); TrailingData = new byte[0]; Encoding = encoding; stream.Seek(0, SeekOrigin.Begin); // Read the Start of Image (SOI) marker. SOI marker is represented // with two bytes: 0xFF, 0xD8. byte[] markerbytes = new byte[2]; if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] != 0xD8) { throw new NotValidJPEGFileException(); } stream.Seek(0, SeekOrigin.Begin); // Search and read sections until we reach the end of file. while (stream.Position != stream.Length) { // Read the next section marker. Section markers are two bytes // with values 0xFF, 0x?? where ?? must not be 0x00 or 0xFF. if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] == 0x00 || markerbytes[1] == 0xFF) { throw new NotValidJPEGFileException(); } JPEGMarker marker = (JPEGMarker)markerbytes[1]; byte[] header = new byte[0]; // SOI, EOI and RST markers do not contain any header if (marker != JPEGMarker.SOI && marker != JPEGMarker.EOI && !(marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) { // Length of the header including the length bytes. // This value is a 16-bit unsigned integer // in big endian byte-order. byte[] lengthbytes = new byte[2]; if (stream.Read(lengthbytes, 0, 2) != 2) { throw new NotValidJPEGFileException(); } long length = (long)BitConverterEx.BigEndian.ToUInt16(lengthbytes, 0); // Read section header. header = new byte[length - 2]; int bytestoread = header.Length; while (bytestoread > 0) { int count = Math.Min(bytestoread, 4 * 1024); int bytesread = stream.Read(header, header.Length - bytestoread, count); if (bytesread == 0) { throw new NotValidJPEGFileException(); } bytestoread -= bytesread; } } // Start of Scan (SOS) sections and RST sections are immediately // followed by entropy coded data. For that, we need to read until // the next section marker once we reach a SOS or RST. byte[] entropydata = new byte[0]; if (marker == JPEGMarker.SOS || (marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) { long position = stream.Position; // Search for the next section marker while (true) { // Search for an 0xFF indicating start of a marker int nextbyte = 0; do { nextbyte = stream.ReadByte(); if (nextbyte == -1) { throw new NotValidJPEGFileException(); } } while ((byte)nextbyte != 0xFF); // Skip filler bytes (0xFF) do { nextbyte = stream.ReadByte(); if (nextbyte == -1) { throw new NotValidJPEGFileException(); } } while ((byte)nextbyte == 0xFF); // Looks like a section marker. The next byte must not be 0x00. if ((byte)nextbyte != 0x00) { // We reached a section marker. Calculate the // length of the entropy coded data. stream.Seek(-2, SeekOrigin.Current); long edlength = stream.Position - position; stream.Seek(-edlength, SeekOrigin.Current); // Read entropy coded data entropydata = new byte[edlength]; int bytestoread = entropydata.Length; while (bytestoread > 0) { int count = Math.Min(bytestoread, 4 * 1024); int bytesread = stream.Read(entropydata, entropydata.Length - bytestoread, count); if (bytesread == 0) { throw new NotValidJPEGFileException(); } bytestoread -= bytesread; } break; } } } // Store section. JPEGSection section = new JPEGSection(marker, header, entropydata); Sections.Add(section); // Some propriety formats store data past the EOI marker if (marker == JPEGMarker.EOI) { int bytestoread = (int)(stream.Length - stream.Position); TrailingData = new byte[bytestoread]; while (bytestoread > 0) { int count = (int)Math.Min(bytestoread, 4 * 1024); int bytesread = stream.Read(TrailingData, TrailingData.Length - bytestoread, count); if (bytesread == 0) { throw new NotValidJPEGFileException(); } bytestoread -= bytesread; } } } // Read metadata sections ReadJFIFAPP0(); ReadJFXXAPP0(); ReadExifAPP1(); // Process the maker note makerNoteProcessed = false; }