public PcxDirectory Extract([NotNull] SequentialReader reader) { reader = reader.WithByteOrder(isMotorolaByteOrder: false); var directory = new PcxDirectory(); try { var identifier = reader.GetSByte(); if (identifier != 0x0A) { throw new ImageProcessingException("Invalid PCX identifier byte"); } directory.Set(PcxDirectory.TagVersion, reader.GetSByte()); var encoding = reader.GetSByte(); if (encoding != 0x01) { throw new ImageProcessingException("Invalid PCX encoding byte"); } directory.Set(PcxDirectory.TagBitsPerPixel, reader.GetByte()); directory.Set(PcxDirectory.TagXMin, reader.GetUInt16()); directory.Set(PcxDirectory.TagYMin, reader.GetUInt16()); directory.Set(PcxDirectory.TagXMax, reader.GetUInt16()); directory.Set(PcxDirectory.TagYMax, reader.GetUInt16()); directory.Set(PcxDirectory.TagHorizontalDpi, reader.GetUInt16()); directory.Set(PcxDirectory.TagVerticalDpi, reader.GetUInt16()); directory.Set(PcxDirectory.TagPalette, reader.GetBytes(48)); reader.Skip(1); directory.Set(PcxDirectory.TagColorPlanes, reader.GetByte()); directory.Set(PcxDirectory.TagBytesPerLine, reader.GetUInt16()); var paletteType = reader.GetUInt16(); if (paletteType != 0) { directory.Set(PcxDirectory.TagPaletteType, paletteType); } var hScrSize = reader.GetUInt16(); if (hScrSize != 0) { directory.Set(PcxDirectory.TagHScrSize, hScrSize); } var vScrSize = reader.GetUInt16(); if (vScrSize != 0) { directory.Set(PcxDirectory.TagVScrSize, vScrSize); } } catch (Exception ex) { directory.AddError("Exception reading PCX file metadata: " + ex.Message); } return(directory); }
public DuckyDirectory Extract(SequentialReader reader) { var directory = new DuckyDirectory(); try { while (true) { var tag = reader.GetUInt16(); // End of Segment is marked with zero if (tag == 0) { break; } var length = reader.GetUInt16(); switch (tag) { case DuckyDirectory.TagQuality: { if (length != 4) { directory.AddError("Unexpected length for the quality tag"); return(directory); } directory.Set(tag, reader.GetUInt32()); break; } case DuckyDirectory.TagComment: case DuckyDirectory.TagCopyright: { reader.Skip(4); directory.Set(tag, reader.GetString(length - 4, Encoding.BigEndianUnicode)); break; } default: { // Unexpected tag directory.Set(tag, reader.GetBytes(length)); break; } } } } catch (IOException e) { directory.AddError(e.Message); } return(directory); }
public virtual void Extract([NotNull] SequentialReader reader, [NotNull] Com.Drew.Metadata.Metadata metadata) { reader.SetMotorolaByteOrder(false); PcxDirectory directory = new PcxDirectory(); metadata.AddDirectory(directory); try { sbyte identifier = reader.GetInt8(); if (identifier != unchecked ((int)(0x0A))) { throw new ImageProcessingException("Invalid PCX identifier byte"); } directory.SetInt(PcxDirectory.TagVersion, reader.GetInt8()); sbyte encoding = reader.GetInt8(); if (encoding != unchecked ((int)(0x01))) { throw new ImageProcessingException("Invalid PCX encoding byte"); } directory.SetInt(PcxDirectory.TagBitsPerPixel, reader.GetUInt8()); directory.SetInt(PcxDirectory.TagXmin, reader.GetUInt16()); directory.SetInt(PcxDirectory.TagYmin, reader.GetUInt16()); directory.SetInt(PcxDirectory.TagXmax, reader.GetUInt16()); directory.SetInt(PcxDirectory.TagYmax, reader.GetUInt16()); directory.SetInt(PcxDirectory.TagHorizontalDpi, reader.GetUInt16()); directory.SetInt(PcxDirectory.TagVerticalDpi, reader.GetUInt16()); directory.SetByteArray(PcxDirectory.TagPalette, reader.GetBytes(48)); reader.Skip(1); directory.SetInt(PcxDirectory.TagColorPlanes, reader.GetUInt8()); directory.SetInt(PcxDirectory.TagBytesPerLine, reader.GetUInt16()); int paletteType = reader.GetUInt16(); if (paletteType != 0) { directory.SetInt(PcxDirectory.TagPaletteType, paletteType); } int hScrSize = reader.GetUInt16(); if (hScrSize != 0) { directory.SetInt(PcxDirectory.TagHscrSize, hScrSize); } int vScrSize = reader.GetUInt16(); if (vScrSize != 0) { directory.SetInt(PcxDirectory.TagVscrSize, vScrSize); } } catch (Exception ex) { directory.AddError("Exception reading PCX file metadata: " + ex.Message); } }
/// <summary>Processes a RIFF data sequence.</summary> /// <param name="reader"> /// the /// <see cref="Com.Drew.Lang.SequentialReader"/> /// from which the data should be read /// </param> /// <param name="handler"> /// the /// <see cref="RiffHandler"/> /// that will coordinate processing and accept read values /// </param> /// <exception cref="RiffProcessingException"> /// if an error occurred during the processing of RIFF data that could not be /// ignored or recovered from /// </exception> /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception> /// <exception cref="Com.Drew.Imaging.Riff.RiffProcessingException"/> public virtual void ProcessRiff([NotNull] SequentialReader reader, [NotNull] RiffHandler handler) { // RIFF files are always little-endian reader.SetMotorolaByteOrder(false); // PROCESS FILE HEADER string fileFourCC = reader.GetString(4); if (!fileFourCC.Equals("RIFF")) { throw new RiffProcessingException("Invalid RIFF header: " + fileFourCC); } // The total size of the chunks that follow plus 4 bytes for the 'WEBP' FourCC int fileSize = reader.GetInt32(); int sizeLeft = fileSize; string identifier = reader.GetString(4); sizeLeft -= 4; if (!handler.ShouldAcceptRiffIdentifier(identifier)) { return; } // PROCESS CHUNKS while (sizeLeft != 0) { string chunkFourCC = reader.GetString(4); int chunkSize = reader.GetInt32(); sizeLeft -= 8; // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as Java cannot // allocate arrays larger than this. if (chunkSize < 0 || sizeLeft < chunkSize) { throw new RiffProcessingException("Invalid RIFF chunk size"); } if (handler.ShouldAcceptChunk(chunkFourCC)) { // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler? handler.ProcessChunk(chunkFourCC, reader.GetBytes(chunkSize)); } else { reader.Skip(chunkSize); } sizeLeft -= chunkSize; // Skip any padding byte added to keep chunks aligned to even numbers of bytes if (chunkSize % 2 == 1) { reader.GetInt8(); sizeLeft--; } } }
private static byte[] GatherBytes(SequentialReader reader) { var bytes = new MemoryStream(); var buffer = new byte[257]; while (true) { var b = reader.GetByte(); if (b == 0) return bytes.ToArray(); buffer[0] = b; reader.GetBytes(buffer, 1, b); bytes.Write(buffer, 0, b + 1); } }
private static byte[] GatherBytes(SequentialReader reader, int firstLength) { var buffer = new MemoryStream(); var length = firstLength; while (length > 0) { buffer.Write(reader.GetBytes(length), 0, length); length = reader.GetByte(); } return buffer.ToArray(); }
// PROCESS CHUNKS private static void ProcessChunks(SequentialReader reader, long maxPosition, IRiffHandler handler) { // Processing chunks. Each chunk is 8 bytes header (4 bytes CC code + 4 bytes length of chunk) + data of the chunk while (reader.Position < maxPosition - 8) { string chunkFourCc = reader.GetString(4, Encoding.ASCII); int chunkSize = reader.GetInt32(); // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this if (chunkSize < 0 || chunkSize + reader.Position > maxPosition) { throw new RiffProcessingException("Invalid RIFF chunk size"); } if (chunkFourCc == "LIST" || chunkFourCc == "RIFF") { string listName = reader.GetString(4, Encoding.ASCII); if (handler.ShouldAcceptList(listName)) { ProcessChunks(reader, reader.Position + chunkSize - 4, handler); } else { reader.Skip(chunkSize - 4); } } else { if (handler.ShouldAcceptChunk(chunkFourCc)) { // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler? handler.ProcessChunk(chunkFourCc, reader.GetBytes(chunkSize)); } else { reader.Skip(chunkSize); } // Skip any padding byte added to keep chunks aligned to even numbers of bytes if (chunkSize % 2 == 1) { reader.Skip(1); } } } }
private static void PopulateEx(WavFormatDirectory directory, SequentialReader reader, int exSize) { if (exSize < 2) { return; } directory.Set(TagValidBitsPerSample, reader.GetUInt16()); if (exSize < 6) { return; } directory.Set(TagChannelMask, reader.GetUInt32()); if (exSize < 22) { return; } directory.Set(TagSubformat, reader.GetBytes(16)); }
public virtual void Extract([NotNull] SequentialReader reader, int length, [NotNull] Com.Drew.Metadata.Metadata metadata) { PhotoshopDirectory directory = new PhotoshopDirectory(); metadata.AddDirectory(directory); // Data contains a sequence of Image Resource Blocks (IRBs): // // 4 bytes - Signature "8BIM" // 2 bytes - Resource identifier // String - Pascal string, padded to make length even // 4 bytes - Size of resource data which follows // Data - The resource data, padded to make size even // // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504 int pos = 0; while (pos < length) { try { // 4 bytes for the signature. Should always be "8BIM". string signature = reader.GetString(4); if (!signature.Equals("8BIM")) { throw new ImageProcessingException("Expecting 8BIM marker"); } pos += 4; // 2 bytes for the resource identifier (tag type). int tagType = reader.GetUInt16(); // segment type pos += 2; // A variable number of bytes holding a pascal string (two leading bytes for length). short descriptionLength = reader.GetUInt8(); pos += 1; // Some basic bounds checking if (descriptionLength < 0 || descriptionLength + pos > length) { throw new ImageProcessingException("Invalid string length"); } // We don't use the string value here reader.Skip(descriptionLength); pos += descriptionLength; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } // 4 bytes for the size of the resource data that follows. int byteCount = reader.GetInt32(); pos += 4; // The resource data. sbyte[] tagBytes = reader.GetBytes(byteCount); pos += byteCount; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } if (tagType == PhotoshopDirectory.TagIptc) { new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), metadata, tagBytes.Length); } else { if (tagType == PhotoshopDirectory.TagIccProfileBytes) { new IccReader().Extract(new ByteArrayReader(tagBytes), metadata); } else { if (tagType == PhotoshopDirectory.TagExifData1 || tagType == PhotoshopDirectory.TagExifData3) { new ExifReader().Extract(new ByteArrayReader(tagBytes), metadata); } else { if (tagType == PhotoshopDirectory.TagXmpData) { new XmpReader().Extract(tagBytes, metadata); } else { directory.SetByteArray(tagType, tagBytes); } } } } 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 (Exception ex) { directory.AddError(ex.Message); return; } } }
public DirectoryList Extract(SequentialReader reader, int length) { var directory = new PhotoshopDirectory(); var directories = new List <Directory> { directory }; // Data contains a sequence of Image Resource Blocks (IRBs): // // 4 bytes - Signature; mostly "8BIM" but "PHUT", "AgHg" and "DCSR" are also found // 2 bytes - Resource identifier // String - Pascal string, padded to make length even // 4 bytes - Size of resource data which follows // Data - The resource data, padded to make size even // // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504 var pos = 0; while (pos < length) { try { // 4 bytes for the signature ("8BIM", "PHUT", etc.) var signature = reader.GetString(4, Encoding.UTF8); pos += 4; // 2 bytes for the resource identifier (tag type). var tagType = reader.GetUInt16(); pos += 2; // A variable number of bytes holding a pascal string (two leading bytes for length). var descriptionLength = reader.GetByte(); pos += 1; // Some basic bounds checking if (descriptionLength + pos > length) { throw new ImageProcessingException("Invalid string length"); } // We don't use the string value here reader.Skip(descriptionLength); pos += descriptionLength; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } // 4 bytes for the size of the resource data that follows. var byteCount = reader.GetInt32(); pos += 4; // The resource data. var tagBytes = reader.GetBytes(byteCount); pos += byteCount; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } // Skip any unsupported IRBs if (signature != "8BIM") { continue; } switch (tagType) { case PhotoshopDirectory.TagIptc: var iptcDirectory = new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), tagBytes.Length); iptcDirectory.Parent = directory; directories.Add(iptcDirectory); break; case PhotoshopDirectory.TagIccProfileBytes: var iccDirectory = new IccReader().Extract(new ByteArrayReader(tagBytes)); iccDirectory.Parent = directory; directories.Add(iccDirectory); break; case PhotoshopDirectory.TagExifData1: case PhotoshopDirectory.TagExifData3: var exifDirectories = new ExifReader().Extract(new ByteArrayReader(tagBytes)); foreach (var exifDirectory in exifDirectories.Where(d => d.Parent == null)) { exifDirectory.Parent = directory; } directories.AddRange(exifDirectories); break; case PhotoshopDirectory.TagXmpData: var xmpDirectory = new XmpReader().Extract(tagBytes); xmpDirectory.Parent = directory; directories.Add(xmpDirectory); break; default: directory.Set(tagType, tagBytes); break; } if (tagType >= 0x0fa0 && tagType <= 0x1387) { PhotoshopDirectory.TagNameMap[tagType] = $"Plug-in {tagType - 0x0fa0 + 1} Data"; } } catch (Exception ex) { directory.AddError(ex.Message); break; } } return(directories); }
public static JpegSegmentData ReadSegments([NotNull] SequentialReader reader, [CanBeNull] ICollection <JpegSegmentType> segmentTypes = null) { // Must be big-endian Debug.Assert(reader.IsMotorolaByteOrder); // first two bytes should be JPEG magic number var magicNumber = reader.GetUInt16(); if (magicNumber != 0xFFD8) { throw new JpegProcessingException($"JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x{magicNumber:X4}"); } var segmentData = new JpegSegmentData(); do { // Find the segment marker. Markers are zero or more 0xFF bytes, followed // by a 0xFF and then a byte not equal to 0x00 or 0xFF. var segmentIdentifier = reader.GetByte(); // We must have at least one 0xFF byte if (segmentIdentifier != 0xFF) { throw new JpegProcessingException($"Expected JPEG segment start identifier 0xFF, not 0x{segmentIdentifier:X2}"); } // Read until we have a non-0xFF byte. This identifies the segment type. var segmentTypeByte = reader.GetByte(); while (segmentTypeByte == 0xFF) { segmentTypeByte = reader.GetByte(); } if (segmentTypeByte == 0) { throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier"); } var segmentType = (JpegSegmentType)segmentTypeByte; if (segmentType == JpegSegmentType.Sos) { // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would // have to search for the two bytes: 0xFF 0xD9 (EOI). // It comes last so simply return at this point return(segmentData); } if (segmentType == JpegSegmentType.Eoi) { // the 'End-Of-Image' segment -- this should never be found in this fashion return(segmentData); } // next 2-bytes are <segment-size>: [high-byte] [low-byte] var segmentLength = (int)reader.GetUInt16(); // segment length includes size bytes, so subtract two segmentLength -= 2; if (segmentLength < 0) { throw new JpegProcessingException("JPEG segment size would be less than zero"); } // Check whether we are interested in this segment if (segmentTypes == null || segmentTypes.Contains(segmentType)) { var segmentBytes = reader.GetBytes(segmentLength); Debug.Assert(segmentLength == segmentBytes.Length); segmentData.AddSegment(segmentType, segmentBytes); } else { // Some of the JPEG is truncated, so just return what data we've already gathered if (!reader.TrySkip(segmentLength)) { return(segmentData); } } }while (true); }
// PROCESS CHUNKS public void ProcessChunks([NotNull] SequentialReader reader, int sizeLeft, [NotNull] IRiffHandler handler) { // Processing chunks. Each chunk is 8 bytes header (4 bytes CC code + 4 bytes length of chunk) + data of the chunk while (reader.Position < sizeLeft) { // Check if end of the file is closer then 8 bytes if (reader.IsCloserToEnd(8)) { return; } string chunkFourCc = reader.GetString(4, Encoding.ASCII); int chunkSize = reader.GetInt32(); sizeLeft -= 8; // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this if (chunkSize < 0 || sizeLeft < chunkSize) { throw new RiffProcessingException("Invalid RIFF chunk size"); } // Check if end of the file is closer then chunkSize bytes if (reader.IsCloserToEnd(chunkSize)) { return; } if (chunkFourCc == "LIST" || chunkFourCc == "RIFF") { string listName = reader.GetString(4, Encoding.ASCII); if (handler.ShouldAcceptList(listName)) { ProcessChunks(reader, sizeLeft - 4, handler); } else { reader.Skip(sizeLeft - 4); } sizeLeft -= chunkSize; } else { if (handler.ShouldAcceptChunk(chunkFourCc)) { // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler? handler.ProcessChunk(chunkFourCc, reader.GetBytes(chunkSize)); } else { reader.Skip(chunkSize); } sizeLeft -= chunkSize; // Skip any padding byte added to keep chunks aligned to even numbers of bytes if (chunkSize % 2 == 1) { reader.GetSByte(); sizeLeft--; } } } }
public IEnumerable <PngChunk> Extract([NotNull] SequentialReader reader, [CanBeNull] ICollection <PngChunkType> desiredChunkTypes) { // // PNG DATA STREAM // // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS. // // PNG SIGNATURE // // Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A // // CHUNK // // 4 - length of the data field (unsigned, but always within 31 bytes), may be zero // 4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z) // * - data field // 4 - CRC calculated from chunk type and chunk data, but not length // // CHUNK TYPES // // Critical Chunk Types: // // IHDR - image header, always the first chunk in the data stream // PLTE - palette table, associated with indexed PNG images // IDAT - image data chunk, of which there may be many // IEND - image trailer, always the last chunk in the data stream // // Ancillary Chunk Types: // // Transparency information: tRNS // Colour space information: cHRM, gAMA, iCCP, sBIT, sRGB // Textual information: iTXt, tEXt, zTXt // Miscellaneous information: bKGD, hIST, pHYs, sPLT // Time information: tIME // // network byte order reader = reader.WithByteOrder(isMotorolaByteOrder: true); if (!_pngSignatureBytes.SequenceEqual(reader.GetBytes(_pngSignatureBytes.Length))) { throw new PngProcessingException("PNG signature mismatch"); } var seenImageHeader = false; var seenImageTrailer = false; var chunks = new List <PngChunk>(); var seenChunkTypes = new HashSet <PngChunkType>(); while (!seenImageTrailer) { // Process the next chunk. var chunkDataLength = reader.GetInt32(); if (chunkDataLength < 0) { throw new PngProcessingException("PNG chunk length exceeds maximum"); } var chunkType = new PngChunkType(reader.GetBytes(4)); var willStoreChunk = desiredChunkTypes == null || desiredChunkTypes.Contains(chunkType); var chunkData = reader.GetBytes(chunkDataLength); // Skip the CRC bytes at the end of the chunk // TODO consider verifying the CRC value to determine if we're processing bad data reader.Skip(4); if (willStoreChunk && seenChunkTypes.Contains(chunkType) && !chunkType.AreMultipleAllowed) { throw new PngProcessingException($"Observed multiple instances of PNG chunk '{chunkType}', for which multiples are not allowed"); } if (chunkType.Equals(PngChunkType.IHDR)) { seenImageHeader = true; } else if (!seenImageHeader) { throw new PngProcessingException($"First chunk should be '{PngChunkType.IHDR}', but '{chunkType}' was observed"); } if (chunkType.Equals(PngChunkType.IEND)) { seenImageTrailer = true; } if (willStoreChunk) { chunks.Add(new PngChunk(chunkType, chunkData)); } seenChunkTypes.Add(chunkType); } return(chunks); }
/// <exception cref="System.IO.IOException"/> private void ProcessTag([NotNull] SequentialReader reader, [NotNull] Com.Drew.Metadata.Directory directory, int directoryType, int tagType, int tagByteCount) { int tagIdentifier = tagType | (directoryType << 8); // Some images have been seen that specify a zero byte tag, which cannot be of much use. // We elect here to completely ignore the tag. The IPTC specification doesn't mention // anything about the interpretation of this situation. // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf if (tagByteCount == 0) { directory.SetString(tagIdentifier, string.Empty); return; } string @string = null; switch (tagIdentifier) { case IptcDirectory.TagCodedCharacterSet: { sbyte[] bytes = reader.GetBytes(tagByteCount); string charset = Iso2022Converter.ConvertISO2022CharsetToJavaCharset(bytes); if (charset == null) { // Unable to determine the charset, so fall through and treat tag as a regular string @string = Sharpen.Runtime.GetStringForBytes(bytes); break; } directory.SetString(tagIdentifier, charset); return; } case IptcDirectory.TagEnvelopeRecordVersion: case IptcDirectory.TagApplicationRecordVersion: case IptcDirectory.TagFileVersion: case IptcDirectory.TagArmVersion: case IptcDirectory.TagProgramVersion: { // short if (tagByteCount >= 2) { int shortValue = reader.GetUInt16(); reader.Skip(tagByteCount - 2); directory.SetInt(tagIdentifier, shortValue); return; } break; } case IptcDirectory.TagUrgency: { // byte directory.SetInt(tagIdentifier, reader.GetUInt8()); reader.Skip(tagByteCount - 1); return; } case IptcDirectory.TagReleaseDate: case IptcDirectory.TagDateCreated: { // Date object if (tagByteCount >= 8) { @string = reader.GetString(tagByteCount); try { int year = System.Convert.ToInt32(Sharpen.Runtime.Substring(@string, 0, 4)); int month = System.Convert.ToInt32(Sharpen.Runtime.Substring(@string, 4, 6)) - 1; int day = System.Convert.ToInt32(Sharpen.Runtime.Substring(@string, 6, 8)); DateTime date = new Sharpen.GregorianCalendar(year, month, day).GetTime(); directory.SetDate(tagIdentifier, date); return; } catch (FormatException) { } } else { // fall through and we'll process the 'string' value below reader.Skip(tagByteCount); } goto case IptcDirectory.TagReleaseTime; } case IptcDirectory.TagReleaseTime: case IptcDirectory.TagTimeCreated: default: { break; } } // time... // fall through // If we haven't returned yet, treat it as a string // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value if (@string == null) { string encoding = directory.GetString(IptcDirectory.TagCodedCharacterSet); if (encoding != null) { @string = reader.GetString(tagByteCount, encoding); } else { sbyte[] bytes_1 = reader.GetBytes(tagByteCount); encoding = Iso2022Converter.GuessEncoding(bytes_1); @string = encoding != null?Sharpen.Runtime.GetStringForBytes(bytes_1, encoding) : Sharpen.Runtime.GetStringForBytes(bytes_1); } } if (directory.ContainsTag(tagIdentifier)) { // this fancy string[] business avoids using an ArrayList for performance reasons string[] oldStrings = directory.GetStringArray(tagIdentifier); string[] newStrings; if (oldStrings == null) { newStrings = new string[1]; } else { newStrings = new string[oldStrings.Length + 1]; System.Array.Copy(oldStrings, 0, newStrings, 0, oldStrings.Length); } newStrings[newStrings.Length - 1] = @string; directory.SetStringArray(tagIdentifier, newStrings); } else { directory.SetString(tagIdentifier, @string); } }
public static JpegSegmentData ReadSegments([NotNull] SequentialReader reader, [CanBeNull] Iterable <JpegSegmentType> segmentTypes) { // Must be big-endian System.Diagnostics.Debug.Assert((reader.IsMotorolaByteOrder())); // first two bytes should be JPEG magic number int magicNumber = reader.GetUInt16(); if (magicNumber != unchecked ((int)(0xFFD8))) { throw new JpegProcessingException("JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x" + Sharpen.Extensions.ToHexString(magicNumber)); } ICollection <sbyte> segmentTypeBytes = null; if (segmentTypes != null) { segmentTypeBytes = new HashSet <sbyte>(); foreach (JpegSegmentType segmentType in segmentTypes) { segmentTypeBytes.Add(segmentType.byteValue); } } JpegSegmentData segmentData = new JpegSegmentData(); do { // Find the segment marker. Markers are zero or more 0xFF bytes, followed // by a 0xFF and then a byte not equal to 0x00 or 0xFF. short segmentIdentifier = reader.GetUInt8(); // We must have at least one 0xFF byte if (segmentIdentifier != unchecked ((int)(0xFF))) { throw new JpegProcessingException("Expected JPEG segment start identifier 0xFF, not 0x" + Sharpen.Extensions.ToHexString(segmentIdentifier).ToUpper()); } // Read until we have a non-0xFF byte. This identifies the segment type. sbyte segmentType = reader.GetInt8(); while (segmentType == unchecked ((sbyte)0xFF)) { segmentType = reader.GetInt8(); } if (segmentType == 0) { throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier"); } if (segmentType == SegmentSos) { // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would // have to search for the two bytes: 0xFF 0xD9 (EOI). // It comes last so simply return at this point return(segmentData); } if (segmentType == MarkerEoi) { // the 'End-Of-Image' segment -- this should never be found in this fashion return(segmentData); } // next 2-bytes are <segment-size>: [high-byte] [low-byte] int segmentLength = reader.GetUInt16(); // segment length includes size bytes, so subtract two segmentLength -= 2; if (segmentLength < 0) { throw new JpegProcessingException("JPEG segment size would be less than zero"); } // Check whether we are interested in this segment if (segmentTypeBytes == null || segmentTypeBytes.Contains(segmentType)) { sbyte[] segmentBytes = reader.GetBytes(segmentLength); System.Diagnostics.Debug.Assert((segmentLength == segmentBytes.Length)); segmentData.AddSegment(segmentType, segmentBytes); } else { // Some if the JPEG is truncated, just return what data we've already gathered if (!reader.TrySkip(segmentLength)) { return(segmentData); } } }while (true); }
Extract([NotNull] SequentialReader reader, int length) { var directory = new PhotoshopDirectory(); var directories = new List <Directory> { directory }; // Data contains a sequence of Image Resource Blocks (IRBs): // // 4 bytes - Signature "8BIM" // 2 bytes - Resource identifier // String - Pascal string, padded to make length even // 4 bytes - Size of resource data which follows // Data - The resource data, padded to make size even // // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504 var pos = 0; while (pos < length) { try { // 4 bytes for the signature. Should always be "8BIM". var signature = reader.GetString(4); pos += 4; if (signature != "8BIM") { throw new ImageProcessingException("Expecting 8BIM marker"); } // 2 bytes for the resource identifier (tag type). var tagType = reader.GetUInt16(); pos += 2; // A variable number of bytes holding a pascal string (two leading bytes for length). var descriptionLength = reader.GetByte(); pos += 1; // Some basic bounds checking if (descriptionLength + pos > length) { throw new ImageProcessingException("Invalid string length"); } // We don't use the string value here reader.Skip(descriptionLength); pos += descriptionLength; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } // 4 bytes for the size of the resource data that follows. var byteCount = reader.GetInt32(); pos += 4; // The resource data. var tagBytes = reader.GetBytes(byteCount); pos += byteCount; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } switch (tagType) { case PhotoshopDirectory.TagIptc: directories.Add(new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), tagBytes.Length)); break; case PhotoshopDirectory.TagIccProfileBytes: directories.Add(new IccReader().Extract(new ByteArrayReader(tagBytes))); break; case PhotoshopDirectory.TagExifData1: case PhotoshopDirectory.TagExifData3: directories.AddRange(new ExifReader().Extract(new ByteArrayReader(tagBytes))); break; case PhotoshopDirectory.TagXmpData: directories.Add(new XmpReader().Extract(tagBytes)); break; default: directory.Set(tagType, tagBytes); break; } if (tagType >= 0x0fa0 && tagType <= 0x1387) { PhotoshopDirectory.TagNameMap[tagType] = $"Plug-in {tagType - 0x0fa0 + 1} Data"; } } catch (Exception ex) { directory.AddError(ex.Message); break; } } return(directories); }
public static IEnumerable <JpegSegment> ReadSegments([NotNull] SequentialReader reader, [CanBeNull] ICollection <JpegSegmentType> segmentTypes = null) { if (!reader.IsMotorolaByteOrder) { throw new JpegProcessingException("Must be big-endian/Motorola byte order."); } // first two bytes should be JPEG magic number var magicNumber = reader.GetUInt16(); if (magicNumber != 0xFFD8) { throw new JpegProcessingException($"JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x{magicNumber:X4}"); } do { // Find the segment marker. Markers are zero or more 0xFF bytes, followed // by a 0xFF and then a byte not equal to 0x00 or 0xFF. var segmentIdentifier = reader.GetByte(); var segmentTypeByte = reader.GetByte(); // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00 while (segmentIdentifier != 0xFF || segmentTypeByte == 0xFF || segmentTypeByte == 0) { segmentIdentifier = segmentTypeByte; segmentTypeByte = reader.GetByte(); } var segmentType = (JpegSegmentType)segmentTypeByte; if (segmentType == JpegSegmentType.Sos) { // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would // have to search for the two bytes: 0xFF 0xD9 (EOI). // It comes last so simply return at this point yield break; } if (segmentType == JpegSegmentType.Eoi) { // the 'End-Of-Image' segment -- this should never be found in this fashion yield break; } // next 2-bytes are <segment-size>: [high-byte] [low-byte] var segmentLength = (int)reader.GetUInt16(); // segment length includes size bytes, so subtract two segmentLength -= 2; // TODO exception strings should end with periods if (segmentLength < 0) { throw new JpegProcessingException("JPEG segment size would be less than zero"); } // Check whether we are interested in this segment if (segmentTypes == null || segmentTypes.Contains(segmentType)) { var segmentOffset = reader.Position; var segmentBytes = reader.GetBytes(segmentLength); Debug.Assert(segmentLength == segmentBytes.Length); yield return(new JpegSegment(segmentType, segmentBytes, segmentOffset)); } else { // Some of the JPEG is truncated, so just return what data we've already gathered if (!reader.TrySkip(segmentLength)) { yield break; } } }while (true); }
/// <exception cref="Com.Drew.Imaging.Png.PngProcessingException"/> /// <exception cref="System.IO.IOException"/> public virtual Iterable <PngChunk> Extract(SequentialReader reader, ICollection <PngChunkType> desiredChunkTypes) { // // PNG DATA STREAM // // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS. // // PNG SIGNATURE // // Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A // // CHUNK // // 4 - length of the data field (unsigned, but always within 31 bytes), may be zero // 4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z) // * - data field // 4 - CRC calculated from chunk type and chunk data, but not length // // CHUNK TYPES // // Critical Chunk Types: // // IHDR - image header, always the first chunk in the data stream // PLTE - palette table, associated with indexed PNG images // IDAT - image data chunk, of which there may be many // IEND - image trailer, always the last chunk in the data stream // // Ancillary Chunk Types: // // Transparency information: tRNS // Colour space information: cHRM, gAMA, iCCP, sBIT, sRGB // Textual information: iTXt, tEXt, zTXt // Miscellaneous information: bKGD, hIST, pHYs, sPLT // Time information: tIME // reader.SetMotorolaByteOrder(true); // network byte order if (!Arrays.Equals(PngSignatureBytes, reader.GetBytes(PngSignatureBytes.Length))) { throw new PngProcessingException("PNG signature mismatch"); } bool seenImageHeader = false; bool seenImageTrailer = false; IList <PngChunk> chunks = new AList <PngChunk>(); ICollection <PngChunkType> seenChunkTypes = new HashSet <PngChunkType>(); while (!seenImageTrailer) { // Process the next chunk. int chunkDataLength = reader.GetInt32(); PngChunkType chunkType = new PngChunkType(reader.GetBytes(4)); sbyte[] chunkData = reader.GetBytes(chunkDataLength); // Skip the CRC bytes at the end of the chunk // TODO consider verifying the CRC value to determine if we're processing bad data reader.Skip(4); if (seenChunkTypes.Contains(chunkType) && !chunkType.AreMultipleAllowed()) { throw new PngProcessingException(Sharpen.Extensions.StringFormat("Observed multiple instances of PNG chunk '%s', for which multiples are not allowed", chunkType)); } if (chunkType.Equals(PngChunkType.Ihdr)) { seenImageHeader = true; } else { if (!seenImageHeader) { throw new PngProcessingException(Sharpen.Extensions.StringFormat("First chunk should be '%s', but '%s' was observed", PngChunkType.Ihdr, chunkType)); } } if (chunkType.Equals(PngChunkType.Iend)) { seenImageTrailer = true; } if (desiredChunkTypes == null || desiredChunkTypes.Contains(chunkType)) { chunks.Add(new PngChunk(chunkType, chunkData)); } seenChunkTypes.Add(chunkType); } return(chunks.AsIterable()); }
private static void ReadBitmapHeader(SequentialReader reader, BmpHeaderDirectory directory, List <Directory> directories) { /* * BITMAPCOREHEADER (12 bytes): * * DWORD Size - Size of this header in bytes * SHORT Width - Image width in pixels * SHORT Height - Image height in pixels * WORD Planes - Number of color planes * WORD BitsPerPixel - Number of bits per pixel * * OS21XBITMAPHEADER (12 bytes): * * DWORD Size - Size of this structure in bytes * WORD Width - Bitmap width in pixels * WORD Height - Bitmap height in pixel * WORD NumPlanes - Number of bit planes (color depth) * WORD BitsPerPixel - Number of bits per pixel per plane * * OS22XBITMAPHEADER (16/64 bytes): * * DWORD Size - Size of this structure in bytes * DWORD Width - Bitmap width in pixels * DWORD Height - Bitmap height in pixel * WORD NumPlanes - Number of bit planes (color depth) * WORD BitsPerPixel - Number of bits per pixel per plane * * - Short version ends here - * * DWORD Compression - Bitmap compression scheme * DWORD ImageDataSize - Size of bitmap data in bytes * DWORD XResolution - X resolution of display device * DWORD YResolution - Y resolution of display device * DWORD ColorsUsed - Number of color table indices used * DWORD ColorsImportant - Number of important color indices * WORD Units - Type of units used to measure resolution * WORD Reserved - Pad structure to 4-byte boundary * WORD Recording - Recording algorithm * WORD Rendering - Halftoning algorithm used * DWORD Size1 - Reserved for halftoning algorithm use * DWORD Size2 - Reserved for halftoning algorithm use * DWORD ColorEncoding - Color model used in bitmap * DWORD Identifier - Reserved for application use * * BITMAPINFOHEADER (40 bytes), BITMAPV2INFOHEADER (52 bytes), BITMAPV3INFOHEADER (56 bytes), * BITMAPV4HEADER (108 bytes) and BITMAPV5HEADER (124 bytes): * * DWORD Size - Size of this header in bytes * LONG Width - Image width in pixels * LONG Height - Image height in pixels * WORD Planes - Number of color planes * WORD BitsPerPixel - Number of bits per pixel * DWORD Compression - Compression methods used * DWORD SizeOfBitmap - Size of bitmap in bytes * LONG HorzResolution - Horizontal resolution in pixels per meter * LONG VertResolution - Vertical resolution in pixels per meter * DWORD ColorsUsed - Number of colors in the image * DWORD ColorsImportant - Minimum number of important colors * * - BITMAPINFOHEADER ends here - * * DWORD RedMask - Mask identifying bits of red component * DWORD GreenMask - Mask identifying bits of green component * DWORD BlueMask - Mask identifying bits of blue component * * - BITMAPV2INFOHEADER ends here - * * DWORD AlphaMask - Mask identifying bits of alpha component * * - BITMAPV3INFOHEADER ends here - * * DWORD CSType - Color space type * LONG RedX - X coordinate of red endpoint * LONG RedY - Y coordinate of red endpoint * LONG RedZ - Z coordinate of red endpoint * LONG GreenX - X coordinate of green endpoint * LONG GreenY - Y coordinate of green endpoint * LONG GreenZ - Z coordinate of green endpoint * LONG BlueX - X coordinate of blue endpoint * LONG BlueY - Y coordinate of blue endpoint * LONG BlueZ - Z coordinate of blue endpoint * DWORD GammaRed - Gamma red coordinate scale value * DWORD GammaGreen - Gamma green coordinate scale value * DWORD GammaBlue - Gamma blue coordinate scale value * * - BITMAPV4HEADER ends here - * * DWORD Intent - Rendering intent for bitmap * DWORD ProfileData - Offset of the profile data relative to BITMAPV5HEADER * DWORD ProfileSize - Size, in bytes, of embedded profile data * DWORD Reserved - Shall be zero * */ try { int bitmapType = directory.GetInt32(BmpHeaderDirectory.TagBitmapType); long headerOffset = reader.Position; int headerSize = reader.GetInt32(); directory.Set(BmpHeaderDirectory.TagHeaderSize, headerSize); /* * Known header type sizes: * * 12 - BITMAPCOREHEADER or OS21XBITMAPHEADER * 16 - OS22XBITMAPHEADER (short) * 40 - BITMAPINFOHEADER * 52 - BITMAPV2INFOHEADER * 56 - BITMAPV3INFOHEADER * 64 - OS22XBITMAPHEADER (full) * 108 - BITMAPV4HEADER * 124 - BITMAPV5HEADER * */ if (headerSize == 12 && bitmapType == (int)BmpHeaderDirectory.BitmapType.Bitmap) { //BITMAPCOREHEADER /* * There's no way to tell BITMAPCOREHEADER and OS21XBITMAPHEADER * apart for the "standard" bitmap type. The difference is only * that BITMAPCOREHEADER has signed width and height while * in OS21XBITMAPHEADER they are unsigned. Since BITMAPCOREHEADER, * the Windows version, is most common, read them as signed. */ directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetInt16()); directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetInt16()); directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16()); } else if (headerSize == 12) { // OS21XBITMAPHEADER directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16()); } else if (headerSize == 16 || headerSize == 64) { // OS22XBITMAPHEADER directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16()); if (headerSize > 16) { directory.Set(BmpHeaderDirectory.TagCompression, reader.GetInt32()); reader.Skip(4); // skip the pixel data length directory.Set(BmpHeaderDirectory.TagXPixelsPerMeter, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagYPixelsPerMeter, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagPaletteColourCount, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagImportantColourCount, reader.GetInt32()); reader.Skip( 2 + // Skip Units, can only be 0 (pixels per meter) 2 + // Skip padding 2 // Skip Recording, can only be 0 (left to right, bottom to top) ); directory.Set(BmpHeaderDirectory.TagRendering, reader.GetUInt16()); reader.Skip(4 + 4); // Skip Size1 and Size2 directory.Set(BmpHeaderDirectory.TagColorEncoding, reader.GetInt32()); reader.Skip(4); // Skip Identifier } } else if ( headerSize == 40 || headerSize == 52 || headerSize == 56 || headerSize == 108 || headerSize == 124) { // BITMAPINFOHEADER V1-5 directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16()); directory.Set(BmpHeaderDirectory.TagCompression, reader.GetInt32()); // skip the pixel data length reader.Skip(4); directory.Set(BmpHeaderDirectory.TagXPixelsPerMeter, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagYPixelsPerMeter, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagPaletteColourCount, reader.GetInt32()); directory.Set(BmpHeaderDirectory.TagImportantColourCount, reader.GetInt32()); if (headerSize == 40) { // BITMAPINFOHEADER end return; } directory.Set(BmpHeaderDirectory.TagRedMask, reader.GetUInt32()); directory.Set(BmpHeaderDirectory.TagGreenMask, reader.GetUInt32()); directory.Set(BmpHeaderDirectory.TagBlueMask, reader.GetUInt32()); if (headerSize == 52) { // BITMAPV2INFOHEADER end return; } directory.Set(BmpHeaderDirectory.TagAlphaMask, reader.GetUInt32()); if (headerSize == 56) { // BITMAPV3INFOHEADER end return; } long csType = reader.GetUInt32(); directory.Set(BmpHeaderDirectory.TagColorSpaceType, csType); reader.Skip(36); // Skip color endpoint coordinates directory.Set(BmpHeaderDirectory.TagGammaRed, reader.GetUInt32()); directory.Set(BmpHeaderDirectory.TagGammaGreen, reader.GetUInt32()); directory.Set(BmpHeaderDirectory.TagGammaBlue, reader.GetUInt32()); if (headerSize == 108) { // BITMAPV4HEADER end return; } directory.Set(BmpHeaderDirectory.TagIntent, reader.GetInt32()); if (csType == (long)BmpHeaderDirectory.ColorSpaceType.ProfileEmbedded || csType == (long)BmpHeaderDirectory.ColorSpaceType.ProfileLinked) { long profileOffset = reader.GetUInt32(); int profileSize = reader.GetInt32(); if (reader.Position > headerOffset + profileOffset) { directory.AddError("Invalid profile data offset 0x" + (headerOffset + profileOffset).ToString("X8")); return; } reader.Skip(headerOffset + profileOffset - reader.Position); if (csType == (long)BmpHeaderDirectory.ColorSpaceType.ProfileLinked) { directory.Set(BmpHeaderDirectory.TagLinkedProfile, reader.GetNullTerminatedString(profileSize, Encoding.GetEncoding(1252))); } else { var iccReader = new ByteArrayReader(reader.GetBytes(profileSize)); var iccDirectory = new IccReader().Extract(iccReader); iccDirectory.Parent = directory; directories.Add(iccDirectory); } } else { reader.Skip( 4 + // Skip ProfileData offset 4 + // Skip ProfileSize 4 // Skip Reserved ); } } else { directory.AddError("Unexpected DIB header size: " + headerSize); } } catch (IOException) { directory.AddError("Unable to read BMP header"); } catch (MetadataException) { directory.AddError("Internal error"); } }
private static void ProcessTag([NotNull] SequentialReader reader, [NotNull] Directory directory, int directoryType, int tagType, int tagByteCount) { var tagIdentifier = tagType | (directoryType << 8); // Some images have been seen that specify a zero byte tag, which cannot be of much use. // We elect here to completely ignore the tag. The IPTC specification doesn't mention // anything about the interpretation of this situation. // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf if (tagByteCount == 0) { directory.Set(tagIdentifier, string.Empty); return; } string str = null; switch (tagIdentifier) { case IptcDirectory.TagCodedCharacterSet: { var bytes = reader.GetBytes(tagByteCount); var charset = Iso2022Converter.ConvertEscapeSequenceToEncodingName(bytes); if (charset == null) { // Unable to determine the charset, so fall through and treat tag as a regular string str = Encoding.UTF8.GetString(bytes); break; } directory.Set(tagIdentifier, charset); return; } case IptcDirectory.TagEnvelopeRecordVersion: case IptcDirectory.TagApplicationRecordVersion: case IptcDirectory.TagFileVersion: case IptcDirectory.TagArmVersion: case IptcDirectory.TagProgramVersion: { // short if (tagByteCount >= 2) { var shortValue = reader.GetUInt16(); reader.Skip(tagByteCount - 2); directory.Set(tagIdentifier, shortValue); return; } break; } case IptcDirectory.TagUrgency: { // byte directory.Set(tagIdentifier, reader.GetByte()); reader.Skip(tagByteCount - 1); return; } case IptcDirectory.TagReleaseDate: case IptcDirectory.TagDateCreated: case IptcDirectory.TagDigitalDateCreated: { // Date object if (tagByteCount >= 8) { str = reader.GetString(tagByteCount); Debug.Assert(str.Length >= 8); int year, month, day; if (int.TryParse(str.Substring(0, 4), out year) && int.TryParse(str.Substring(4, 2), out month) && int.TryParse(str.Substring(6, 2), out day) && DateUtil.IsValidDate(year, month, day)) { directory.Set(tagIdentifier, new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Unspecified)); return; } } else { // fall through and we'll process the 'string' value below reader.Skip(tagByteCount); } break; } } // If we haven't returned yet, treat it as a string // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value if (str == null) { var encodingName = directory.GetString(IptcDirectory.TagCodedCharacterSet); Encoding encoding = null; if (encodingName != null) { try { encoding = Encoding.GetEncoding(encodingName); } catch { } } var bytes = reader.GetBytes(tagByteCount); if (encoding == null) { encoding = Iso2022Converter.GuessEncoding(bytes); } if (encoding == null) { encoding = Encoding.UTF8; } str = encoding.GetString(bytes); } if (directory.ContainsTag(tagIdentifier)) { // this fancy string[] business avoids using an ArrayList for performance reasons var oldStrings = directory.GetStringArray(tagIdentifier); string[] newStrings; if (oldStrings == null) { // TODO hitting this block means any prior value(s) are discarded newStrings = new string[1]; } else { newStrings = new string[oldStrings.Length + 1]; Array.Copy(oldStrings, 0, newStrings, 0, oldStrings.Length); } newStrings[newStrings.Length - 1] = str; directory.Set(tagIdentifier, newStrings); } else { directory.Set(tagIdentifier, str); } }
public DirectoryList Extract(SequentialReader reader, int length) { var directory = new PhotoshopDirectory(); var directories = new List <Directory> { directory }; // Data contains a sequence of Image Resource Blocks (IRBs): // // 4 bytes - Signature; mostly "8BIM" but "PHUT", "AgHg" and "DCSR" are also found // 2 bytes - Resource identifier // String - Pascal string, padded to make length even // 4 bytes - Size of resource data which follows // Data - The resource data, padded to make size even // // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504 var pos = 0; int clippingPathCount = 0; while (pos < length) { try { // 4 bytes for the signature ("8BIM", "PHUT", etc.) var signature = reader.GetString(4, Encoding.UTF8); pos += 4; // 2 bytes for the resource identifier (tag type). var tagType = reader.GetUInt16(); pos += 2; // A variable number of bytes holding a pascal string (two leading bytes for length). var descriptionLength = reader.GetByte(); pos += 1; // Some basic bounds checking if (descriptionLength + pos > length) { throw new ImageProcessingException("Invalid string length"); } // Get name (important for paths) var description = new StringBuilder(); // Loop through each byte and append to string while (descriptionLength > 0) { description.Append((char)reader.GetByte()); pos++; descriptionLength--; } // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } // 4 bytes for the size of the resource data that follows. var byteCount = reader.GetInt32(); pos += 4; // The resource data. var tagBytes = reader.GetBytes(byteCount); pos += byteCount; // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.Skip(1); pos++; } // Skip any unsupported IRBs if (signature != "8BIM") { continue; } switch (tagType) { case PhotoshopDirectory.TagIptc: var iptcDirectory = new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), tagBytes.Length); iptcDirectory.Parent = directory; directories.Add(iptcDirectory); break; case PhotoshopDirectory.TagIccProfileBytes: var iccDirectory = new IccReader().Extract(new ByteArrayReader(tagBytes)); iccDirectory.Parent = directory; directories.Add(iccDirectory); break; case PhotoshopDirectory.TagExifData1: case PhotoshopDirectory.TagExifData3: var exifDirectories = new ExifReader().Extract(new ByteArrayReader(tagBytes)); foreach (var exifDirectory in exifDirectories.Where(d => d.Parent == null)) { exifDirectory.Parent = directory; } directories.AddRange(exifDirectories); break; case PhotoshopDirectory.TagXmpData: var xmpDirectory = new XmpReader().Extract(tagBytes); xmpDirectory.Parent = directory; directories.Add(xmpDirectory); break; default: if (tagType >= PhotoshopDirectory.TagClippingPathBlockStart && tagType <= PhotoshopDirectory.TagClippingPathBlockEnd) { clippingPathCount++; Array.Resize(ref tagBytes, tagBytes.Length + description.Length + 1); // Append description(name) to end of byte array with 1 byte before the description representing the length for (int i = tagBytes.Length - description.Length - 1; i < tagBytes.Length; i++) { if (i % (tagBytes.Length - description.Length - 1 + description.Length) == 0) { tagBytes[i] = (byte)description.Length; } else { tagBytes[i] = (byte)description[i - (tagBytes.Length - description.Length - 1)]; } } PhotoshopDirectory.TagNameMap[PhotoshopDirectory.TagClippingPathBlockStart + clippingPathCount - 1] = "Path Info " + clippingPathCount; directory.Set(PhotoshopDirectory.TagClippingPathBlockStart + clippingPathCount - 1, tagBytes); } else { directory.Set(tagType, tagBytes); } break; } if (tagType >= 0x0fa0 && tagType <= 0x1387) { PhotoshopDirectory.TagNameMap[tagType] = $"Plug-in {tagType - 0x0fa0 + 1} Data"; } } catch (Exception ex) { directory.AddError(ex.Message); break; } } return(directories); }
/// <summary>Processes a RIFF data sequence.</summary> /// <param name="reader">The <see cref="SequentialReader"/> from which the data should be read.</param> /// <param name="handler">The <see cref="IRiffHandler"/> that will coordinate processing and accept read values.</param> /// <exception cref="RiffProcessingException">An error occurred during the processing of RIFF data that could not be ignored or recovered from.</exception> /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception> public void ProcessRiff([NotNull] SequentialReader reader, [NotNull] IRiffHandler handler) { // RIFF files are always little-endian reader = reader.WithByteOrder(isMotorolaByteOrder: false); // PROCESS FILE HEADER var fileFourCc = reader.GetString(4, Encoding.UTF8); if (fileFourCc != "RIFF") { throw new RiffProcessingException("Invalid RIFF header: " + fileFourCc); } // The total size of the chunks that follow plus 4 bytes for the 'WEBP' FourCC var fileSize = reader.GetInt32(); var sizeLeft = fileSize; var identifier = reader.GetString(4, Encoding.UTF8); sizeLeft -= 4; if (!handler.ShouldAcceptRiffIdentifier(identifier)) { return; } // PROCESS CHUNKS while (sizeLeft != 0) { var chunkFourCc = reader.GetString(4, Encoding.UTF8); var chunkSize = reader.GetInt32(); sizeLeft -= 8; // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this if (chunkSize < 0 || sizeLeft < chunkSize) { throw new RiffProcessingException("Invalid RIFF chunk size"); } if (handler.ShouldAcceptChunk(chunkFourCc)) { // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler? handler.ProcessChunk(chunkFourCc, reader.GetBytes(chunkSize)); } else { reader.Skip(chunkSize); } sizeLeft -= chunkSize; // Skip any padding byte added to keep chunks aligned to even numbers of bytes if (chunkSize % 2 == 1) { reader.GetSByte(); sizeLeft--; } } }
private static void ProcessTag(SequentialReader reader, Directory directory, int directoryType, int tagType, int tagByteCount) { var tagIdentifier = tagType | (directoryType << 8); // Some images have been seen that specify a zero byte tag, which cannot be of much use. // We elect here to completely ignore the tag. The IPTC specification doesn't mention // anything about the interpretation of this situation. // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf if (tagByteCount == 0) { directory.Set(tagIdentifier, string.Empty); return; } switch (tagIdentifier) { case IptcDirectory.TagCodedCharacterSet: { var bytes = reader.GetBytes(tagByteCount); var charset = Iso2022Converter.ConvertEscapeSequenceToEncodingName(bytes); if (charset == null) { // Unable to determine the charset, so fall through and treat tag as a regular string charset = Encoding.UTF8.GetString(bytes, 0, bytes.Length); } directory.Set(tagIdentifier, charset); return; } case IptcDirectory.TagEnvelopeRecordVersion: case IptcDirectory.TagApplicationRecordVersion: case IptcDirectory.TagFileVersion: case IptcDirectory.TagArmVersion: case IptcDirectory.TagProgramVersion: { // short if (tagByteCount == 2) { var shortValue = reader.GetUInt16(); reader.Skip(tagByteCount - 2); directory.Set(tagIdentifier, shortValue); return; } break; } case IptcDirectory.TagUrgency: { // byte directory.Set(tagIdentifier, reader.GetByte()); reader.Skip(tagByteCount - 1); return; } } // If we haven't returned yet, treat it as a string // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value var encodingName = directory.GetString(IptcDirectory.TagCodedCharacterSet); Encoding?encoding = null; if (encodingName != null) { try { encoding = Encoding.GetEncoding(encodingName); } catch (ArgumentException) { } } StringValue str; if (encoding != null) { str = reader.GetStringValue(tagByteCount, encoding); } else { var bytes = reader.GetBytes(tagByteCount); encoding = Iso2022Converter.GuessEncoding(bytes); str = new StringValue(bytes, encoding); } if (directory.ContainsTag(tagIdentifier)) { // this fancy string[] business avoids using an ArrayList for performance reasons var oldStrings = directory.GetStringValueArray(tagIdentifier); StringValue[] newStrings; if (oldStrings == null) { // TODO hitting this block means any prior value(s) are discarded newStrings = new StringValue[1]; } else { newStrings = new StringValue[oldStrings.Length + 1]; Array.Copy(oldStrings, 0, newStrings, 0, oldStrings.Length); } newStrings[newStrings.Length - 1] = str; directory.Set(tagIdentifier, newStrings); } else { directory.Set(tagIdentifier, str); } }
public PixelInformationBox(BoxLocation location, SequentialReader reader) : base(location, reader) { ChannelCount = reader.GetByte(); BitsPerChannel = reader.GetBytes(ChannelCount); }