public virtual void Extract(RandomAccessReader reader, Com.Drew.Metadata.Metadata metadata) { PhotoshopDirectory directory = metadata.GetOrCreateDirectory<PhotoshopDirectory>(); int pos; try { pos = reader.GetString(0, 13).Equals("Photoshop 3.0") ? 14 : 0; } catch (IOException) { directory.AddError("Unable to read header"); return; } long length; try { length = reader.GetLength(); } catch (IOException e) { directory.AddError("Unable to read Photoshop data: " + e.Message); return; } while (pos < length) { try { // 4 bytes for the signature. Should always be "8BIM". //String signature = new String(data, pos, 4); pos += 4; // 2 bytes for the resource identifier (tag type). int tagType = reader.GetUInt16(pos); // segment type pos += 2; // A variable number of bytes holding a pascal string (two leading bytes for length). int descriptionLength = reader.GetUInt16(pos); pos += 2; // Some basic bounds checking if (descriptionLength < 0 || descriptionLength + pos > length) { return; } //String description = new String(data, pos, descriptionLength); pos += descriptionLength; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { pos++; } // 4 bytes for the size of the resource data that follows. int byteCount = reader.GetInt32(pos); pos += 4; // The resource data. sbyte[] tagBytes = reader.GetBytes(pos, byteCount); pos += byteCount; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { pos++; } directory.SetByteArray(tagType, tagBytes); // TODO allow rebasing the reader with a new zero-point, rather than copying data here if (tagType == PhotoshopDirectory.TagIptc) { new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), metadata, tagBytes.Length); } if (tagType >= unchecked((int)(0x0fa0)) && tagType <= unchecked((int)(0x1387))) { PhotoshopDirectory._tagNameMap.Put(tagType, Sharpen.Extensions.StringFormat("Plug-in %d Data", tagType - unchecked((int)(0x0fa0)) + 1)); } } catch (IOException ex) { directory.AddError(ex.Message); return; } } }
private static void ProcessKodakMakernote(KodakMakernoteDirectory directory, int tagValueOffset, RandomAccessReader reader) { // Kodak's makernote is not in IFD format. It has values at fixed offsets. int dataOffset = tagValueOffset + 8; try { directory.SetString(KodakMakernoteDirectory.TagKodakModel, reader.GetString(dataOffset, 8)); directory.SetInt(KodakMakernoteDirectory.TagQuality, reader.GetUInt8(dataOffset + 9)); directory.SetInt(KodakMakernoteDirectory.TagBurstMode, reader.GetUInt8(dataOffset + 10)); directory.SetInt(KodakMakernoteDirectory.TagImageWidth, reader.GetUInt16(dataOffset + 12)); directory.SetInt(KodakMakernoteDirectory.TagImageHeight, reader.GetUInt16(dataOffset + 14)); directory.SetInt(KodakMakernoteDirectory.TagYearCreated, reader.GetUInt16(dataOffset + 16)); directory.SetByteArray(KodakMakernoteDirectory.TagMonthDayCreated, reader.GetBytes(dataOffset + 18, 2)); directory.SetByteArray(KodakMakernoteDirectory.TagTimeCreated, reader.GetBytes(dataOffset + 20, 4)); directory.SetInt(KodakMakernoteDirectory.TagBurstMode2, reader.GetUInt16(dataOffset + 24)); directory.SetInt(KodakMakernoteDirectory.TagShutterMode, reader.GetUInt8(dataOffset + 27)); directory.SetInt(KodakMakernoteDirectory.TagMeteringMode, reader.GetUInt8(dataOffset + 28)); directory.SetInt(KodakMakernoteDirectory.TagSequenceNumber, reader.GetUInt8(dataOffset + 29)); directory.SetInt(KodakMakernoteDirectory.TagFNumber, reader.GetUInt16(dataOffset + 30)); directory.SetLong(KodakMakernoteDirectory.TagExposureTime, reader.GetUInt32(dataOffset + 32)); directory.SetInt(KodakMakernoteDirectory.TagExposureCompensation, reader.GetInt16(dataOffset + 36)); directory.SetInt(KodakMakernoteDirectory.TagFocusMode, reader.GetUInt8(dataOffset + 56)); directory.SetInt(KodakMakernoteDirectory.TagWhiteBalance, reader.GetUInt8(dataOffset + 64)); directory.SetInt(KodakMakernoteDirectory.TagFlashMode, reader.GetUInt8(dataOffset + 92)); directory.SetInt(KodakMakernoteDirectory.TagFlashFired, reader.GetUInt8(dataOffset + 93)); directory.SetInt(KodakMakernoteDirectory.TagIsoSetting, reader.GetUInt16(dataOffset + 94)); directory.SetInt(KodakMakernoteDirectory.TagIso, reader.GetUInt16(dataOffset + 96)); directory.SetInt(KodakMakernoteDirectory.TagTotalZoom, reader.GetUInt16(dataOffset + 98)); directory.SetInt(KodakMakernoteDirectory.TagDateTimeStamp, reader.GetUInt16(dataOffset + 100)); directory.SetInt(KodakMakernoteDirectory.TagColorMode, reader.GetUInt16(dataOffset + 102)); directory.SetInt(KodakMakernoteDirectory.TagDigitalZoom, reader.GetUInt16(dataOffset + 104)); directory.SetInt(KodakMakernoteDirectory.TagSharpness, reader.GetInt8(dataOffset + 107)); } catch (IOException ex) { directory.AddError("Error processing Kodak makernote data: " + ex.Message); } }
private static void ExtractTiff(RandomAccessReader reader, Com.Drew.Metadata.Metadata metadata, Com.Drew.Metadata.Directory firstDirectory, int tiffHeaderOffset) { // this should be either "MM" or "II" string byteOrderIdentifier = reader.GetString(tiffHeaderOffset, 2); if ("MM".Equals(byteOrderIdentifier)) { reader.SetMotorolaByteOrder(true); } else { if ("II".Equals(byteOrderIdentifier)) { reader.SetMotorolaByteOrder(false); } else { firstDirectory.AddError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier); return; } } // Check the next two values for correctness. int tiffMarker = reader.GetUInt16(2 + tiffHeaderOffset); int standardTiffMarker = unchecked((int)(0x002A)); int olympusRawTiffMarker = unchecked((int)(0x4F52)); // for ORF files int panasonicRawTiffMarker = unchecked((int)(0x0055)); // for RW2 files if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) { firstDirectory.AddError("Unexpected TIFF marker after byte order identifier: 0x" + Sharpen.Extensions.ToHexString(tiffMarker)); return; } int firstIfdOffset = reader.GetInt32(4 + tiffHeaderOffset) + tiffHeaderOffset; // David Ekholm sent a digital camera image that has this problem // TODO getLength should be avoided as it causes RandomAccessStreamReader to read to the end of the stream if (firstIfdOffset >= reader.GetLength() - 1) { firstDirectory.AddError("First Exif directory offset is beyond end of Exif data segment"); // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case firstIfdOffset = 14; } ICollection<int> processedIfdOffsets = new HashSet<int>(); ProcessIFD(firstDirectory, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset, metadata, reader); // after the extraction process, if we have the correct tags, we may be able to store thumbnail information ExifThumbnailDirectory thumbnailDirectory = metadata.GetDirectory<ExifThumbnailDirectory>(); if (thumbnailDirectory != null && thumbnailDirectory.ContainsTag(ExifThumbnailDirectory.TagThumbnailCompression)) { int? offset = thumbnailDirectory.GetInteger(ExifThumbnailDirectory.TagThumbnailOffset); int? length = thumbnailDirectory.GetInteger(ExifThumbnailDirectory.TagThumbnailLength); if (offset != null && length != null) { try { sbyte[] thumbnailData = reader.GetBytes(tiffHeaderOffset + offset.Value, length.Value); thumbnailDirectory.SetThumbnailData(thumbnailData); } catch (IOException ex) { firstDirectory.AddError("Invalid thumbnail data specification: " + ex.Message); } } } }
private static void ProcessMakernote(int makernoteOffset, ICollection<int> processedIfdOffsets, int tiffHeaderOffset, Com.Drew.Metadata.Metadata metadata, RandomAccessReader reader) { // Determine the camera model and makernote format Com.Drew.Metadata.Directory ifd0Directory = metadata.GetDirectory<ExifIFD0Directory>(); if (ifd0Directory == null) { return; } string cameraMake = ifd0Directory.GetString(ExifIFD0Directory.TagMake); string firstThreeChars = reader.GetString(makernoteOffset, 3); string firstFourChars = reader.GetString(makernoteOffset, 4); string firstFiveChars = reader.GetString(makernoteOffset, 5); string firstSixChars = reader.GetString(makernoteOffset, 6); string firstSevenChars = reader.GetString(makernoteOffset, 7); string firstEightChars = reader.GetString(makernoteOffset, 8); string firstTwelveChars = reader.GetString(makernoteOffset, 12); bool byteOrderBefore = reader.IsMotorolaByteOrder(); if ("OLYMP".Equals(firstFiveChars) || "EPSON".Equals(firstFiveChars) || "AGFA".Equals(firstFourChars)) { // Olympus Makernote // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ ProcessIFD(metadata.GetOrCreateDirectory<OlympusMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset, metadata, reader); } else { if (cameraMake != null && Sharpen.Extensions.Trim(cameraMake).ToUpper().StartsWith("NIKON")) { if ("Nikon".Equals(firstFiveChars)) { switch (reader.GetUInt8(makernoteOffset + 6)) { case 1: { /* There are two scenarios here: * Type 1: ** * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon........... * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................ * Type 3: ** * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*... * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200 */ ProcessIFD(metadata.GetOrCreateDirectory<NikonType1MakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset, metadata, reader); break; } case 2: { ProcessIFD(metadata.GetOrCreateDirectory<NikonType2MakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 18, makernoteOffset + 10, metadata, reader); break; } default: { ifd0Directory.AddError("Unsupported Nikon makernote data ignored."); break; } } } else { // The IFD begins with the first Makernote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models. ProcessIFD(metadata.GetOrCreateDirectory<NikonType2MakernoteDirectory>(), processedIfdOffsets, makernoteOffset, tiffHeaderOffset, metadata, reader); } } else { if ("SONY CAM".Equals(firstEightChars) || "SONY DSC".Equals(firstEightChars)) { ProcessIFD(metadata.GetOrCreateDirectory<SonyType1MakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset, metadata, reader); } else { if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".Equals(firstTwelveChars)) { // force MM for this directory reader.SetMotorolaByteOrder(true); // skip 12 byte header + 2 for "MM" + 6 ProcessIFD(metadata.GetOrCreateDirectory<SonyType6MakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 20, tiffHeaderOffset, metadata, reader); } else { if ("SIGMA\u0000\u0000\u0000".Equals(firstEightChars) || "FOVEON\u0000\u0000".Equals(firstEightChars)) { ProcessIFD(metadata.GetOrCreateDirectory<SigmaMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 10, tiffHeaderOffset, metadata, reader); } else { if ("KDK".Equals(firstThreeChars)) { reader.SetMotorolaByteOrder(firstSevenChars.Equals("KDK INFO")); ProcessKodakMakernote(metadata.GetOrCreateDirectory<KodakMakernoteDirectory>(), makernoteOffset, reader); } else { if (Sharpen.Runtime.EqualsIgnoreCase("Canon", cameraMake)) { ProcessIFD(metadata.GetOrCreateDirectory<CanonMakernoteDirectory>(), processedIfdOffsets, makernoteOffset, tiffHeaderOffset, metadata, reader); } else { if (cameraMake != null && cameraMake.ToUpper().StartsWith("CASIO")) { if ("QVC\u0000\u0000\u0000".Equals(firstSixChars)) { ProcessIFD(metadata.GetOrCreateDirectory<CasioType2MakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 6, tiffHeaderOffset, metadata, reader); } else { ProcessIFD(metadata.GetOrCreateDirectory<CasioType1MakernoteDirectory>(), processedIfdOffsets, makernoteOffset, tiffHeaderOffset, metadata, reader); } } else { if ("FUJIFILM".Equals(firstEightChars) || Sharpen.Runtime.EqualsIgnoreCase("Fujifilm", cameraMake)) { // Note that this also applies to certain Leica cameras, such as the Digilux-4.3 reader.SetMotorolaByteOrder(false); // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote // IFD, though the offset is relative to the start of the makernote, not the TIFF // header (like everywhere else) int ifdStart = makernoteOffset + reader.GetInt32(makernoteOffset + 8); ProcessIFD(metadata.GetOrCreateDirectory<FujifilmMakernoteDirectory>(), processedIfdOffsets, ifdStart, makernoteOffset, metadata, reader); } else { if (cameraMake != null && cameraMake.ToUpper().StartsWith("MINOLTA")) { // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote // area that commences immediately. ProcessIFD(metadata.GetOrCreateDirectory<OlympusMakernoteDirectory>(), processedIfdOffsets, makernoteOffset, tiffHeaderOffset, metadata, reader); } else { if ("KYOCERA".Equals(firstSevenChars)) { // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html ProcessIFD(metadata.GetOrCreateDirectory<KyoceraMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 22, tiffHeaderOffset, metadata, reader); } else { if ("LEICA".Equals(firstFiveChars)) { reader.SetMotorolaByteOrder(false); if ("Leica Camera AG".Equals(cameraMake)) { ProcessIFD(metadata.GetOrCreateDirectory<LeicaMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset, metadata, reader); } else { if ("LEICA".Equals(cameraMake)) { // Some Leica cameras use Panasonic makernote tags ProcessIFD(metadata.GetOrCreateDirectory<PanasonicMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset, metadata, reader); } } } else { if ("Panasonic\u0000\u0000\u0000".Equals(reader.GetString(makernoteOffset, 12))) { // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html ProcessIFD(metadata.GetOrCreateDirectory<PanasonicMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset, metadata, reader); } else { if ("AOC\u0000".Equals(firstFourChars)) { // NON-Standard TIFF IFD Data using Casio Type 2 Tags // IFD has no Next-IFD pointer at end of IFD, and // Offsets are relative to the start of the current IFD tag, not the TIFF header // Observed for: // - Pentax ist D ProcessIFD(metadata.GetOrCreateDirectory<CasioType2MakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 6, makernoteOffset, metadata, reader); } else { if (cameraMake != null && (cameraMake.ToUpper().StartsWith("PENTAX") || cameraMake.ToUpper().StartsWith("ASAHI"))) { // NON-Standard TIFF IFD Data using Pentax Tags // IFD has no Next-IFD pointer at end of IFD, and // Offsets are relative to the start of the current IFD tag, not the TIFF header // Observed for: // - PENTAX Optio 330 // - PENTAX Optio 430 ProcessIFD(metadata.GetOrCreateDirectory<PentaxMakernoteDirectory>(), processedIfdOffsets, makernoteOffset, makernoteOffset, metadata, reader); } else { // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) { // // This Konica data is not understood. Header identified in accordance with information at this site: // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html // // TODO add support for minolta/konica cameras // exifDirectory.addError("Unsupported Konica/Minolta data ignored."); if ("SANYO\x0\x1\x0".Equals(firstEightChars)) { ProcessIFD(metadata.GetOrCreateDirectory<SanyoMakernoteDirectory>(), processedIfdOffsets, makernoteOffset + 8, makernoteOffset, metadata, reader); } } } } } } } } } } } } } } } } // The makernote is not comprehended by this library. // If you are reading this and believe a particular camera's image should be processed, get in touch. reader.SetMotorolaByteOrder(byteOrderBefore); }
public virtual void TestGetString() { sbyte[] bytes = new sbyte[] { unchecked ((int)(0x41)), unchecked ((int)(0x42)), unchecked ((int)(0x43)), unchecked ((int)(0x44)), unchecked ((int)(0x00)), unchecked ((int)(0x45)), unchecked ((int)(0x46)), unchecked ((int)(0x47)) }; RandomAccessReader reader = CreateReader(bytes); Sharpen.Tests.AreEqual(string.Empty, reader.GetString(0, 0)); Sharpen.Tests.AreEqual("A", reader.GetString(0, 1)); Sharpen.Tests.AreEqual("AB", reader.GetString(0, 2)); Sharpen.Tests.AreEqual("ABC", reader.GetString(0, 3)); Sharpen.Tests.AreEqual("ABCD", reader.GetString(0, 4)); Sharpen.Tests.AreEqual("ABCD\x0", reader.GetString(0, 5)); Sharpen.Tests.AreEqual("ABCD\x0000E", reader.GetString(0, 6)); Sharpen.Tests.AreEqual("BCD", reader.GetString(1, 3)); Sharpen.Tests.AreEqual("BCD\x0", reader.GetString(1, 4)); Sharpen.Tests.AreEqual("BCD\x0000E", reader.GetString(1, 5)); Sharpen.Tests.AreEqual("\x0000EF", reader.GetString(4, 3)); }