using System; using System.IO; namespace ExifLib { /// /// Based on http://www.media.mit.edu/pia/Research/deepview/exif.html /// http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html /// public class ExifReader { public JpegInfo info { get; private set; } private bool littleEndian = false; public static JpegInfo ReadJpeg(byte[] fiBYTES, string Name) { DateTime then = DateTime.Now; using (MemoryStream fs = new MemoryStream(fiBYTES)) { ExifReader reader = new ExifReader(fs); reader.info.FileSize = (int)fs.Length; reader.info.FileName = Name; reader.info.LoadTime = (DateTime.Now - then); return reader.info; } } protected ExifReader(Stream stream) { info = new JpegInfo(); int a = stream.ReadByte(); // ensure SOI marker if (a != JpegId.START || stream.ReadByte() != JpegId.SOI) return; info.IsValid = true; for (; ; ) { int marker = 0, prev = 0; // find next marker for (a = 0; ; ++a) { marker = stream.ReadByte(); if (marker != JpegId.START && prev == JpegId.START) break; prev = marker; } // read section length int lenHigh = stream.ReadByte(); int lenLow = stream.ReadByte(); int itemlen = (lenHigh << 8) | lenLow; // read the section byte[] section = new byte[itemlen]; section[0] = (byte)lenHigh; section[1] = (byte)lenLow; int bytesRead = stream.Read(section, 2, itemlen - 2); if (bytesRead != itemlen - 2) return; switch (marker) { case JpegId.SOS: // start of stream: and we're done return; case JpegId.EOI: // no data? no good. return; case JpegId.EXIF: { if (section[2] == 'E' && section[3] == 'x' && section[4] == 'i' && section[5] == 'f') { ProcessExif(section); } } break; case JpegId.IPTC: { // don't care. } break; case 0xC0: case 0xC1: case 0xC2: case 0xC3: // case 0xC4: // not SOF case 0xC5: case 0xC6: case 0xC7: // case 0xC8: // not SOF case 0xC9: case 0xCA: case 0xCB: // case 0xCC: // not SOF case 0xCD: case 0xCE: case 0xCF: { ProcessSOF(section, marker); } break; default: { // don't care. } break; } section = null; GC.Collect(); } } private void ProcessExif(byte[] section) { int idx = 6; if (section[idx++] != 0 || section[idx++] != 0) { // "Exif" is not followed by 2 null bytes. return; } if (section[idx] == 'I' && section[idx + 1] == 'I') { // intel order littleEndian = true; } else { if (section[idx] == 'M' && section[idx + 1] == 'M') littleEndian = false; else { // unknown order... return; } } idx += 2; int a = ExifIO.ReadUShort(section, idx, littleEndian); idx += 2; if (a != 0x002A) { // bad start... return; } a = ExifIO.ReadInt(section, idx, littleEndian); idx += 4; if (a < 8 || a > 16) { if (a < 16 || a > section.Length - 16) { // invalid offset return; } } ProcessExifDir(section, a + 8, 8, section.Length - 8, 0, ExifIFD.Exif); } private int DirOffset(int start, int num) { return start + 2 + 12 * num; } private void ProcessExifDir(byte[] section, int offsetDir, int offsetBase, int length, int depth, ExifIFD ifd) { if (depth > 4) { // corrupted Exif header... return; } ushort numEntries = ExifIO.ReadUShort(section, offsetDir, littleEndian); if (offsetDir + 2 + 12 * numEntries >= offsetDir + length) { // too long return; } int offset = 0; for (int de = 0; de < numEntries; ++de) { offset = DirOffset(offsetDir, de); ExifTag exifTag = new ExifTag(section, offset, offsetBase, length, littleEndian); if (!exifTag.IsValid) continue; switch (exifTag.Tag) { case (int)ExifIFD.Exif: { int dirStart = offsetBase + exifTag.GetInt(0); if (dirStart <= offsetBase + length) { ProcessExifDir(section, dirStart, offsetBase, length, depth + 1, ExifIFD.Exif); } } break; case (int)ExifIFD.Gps: { int dirStart = offsetBase + exifTag.GetInt(0); if (dirStart <= offsetBase + length) { ProcessExifDir(section, dirStart, offsetBase, length, depth + 1, ExifIFD.Gps); } } break; default: { exifTag.Populate(info, ifd); } break; } } // final link defined? offset = DirOffset(offsetDir, numEntries) + 4; if (offset <= offsetBase + length) { offset = ExifIO.ReadInt(section, offsetDir + 2 + 12 * numEntries, littleEndian); if (offset > 0) { int subDirStart = offsetBase + offset; if (subDirStart <= offsetBase + length && subDirStart >= offsetBase) { ProcessExifDir(section, subDirStart, offsetBase, length, depth + 1, ifd); } } } if (info.ThumbnailData == null && info.ThumbnailOffset > 0 && info.ThumbnailSize > 0) { // store it. info.ThumbnailData = new byte[info.ThumbnailSize]; Array.Copy(section, offsetBase + info.ThumbnailOffset, info.ThumbnailData, 0, info.ThumbnailSize); } } private void ProcessSOF(byte[] section, int marker) { // bytes 1,2 is section len // byte 3 is precision (bytes per sample) info.Height = ((int)section[3] << 8) | (int)section[4]; info.Width = ((int)section[5] << 8) | (int)section[6]; int components = (int)section[7]; info.IsColor = (components == 3); } } }