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); }
private static GifImageDirectory ReadImageBlock(SequentialReader reader) { var imageDirectory = new GifImageDirectory(); imageDirectory.Set(GifImageDirectory.TagLeft, reader.GetUInt16()); imageDirectory.Set(GifImageDirectory.TagTop, reader.GetUInt16()); imageDirectory.Set(GifImageDirectory.TagWidth, reader.GetUInt16()); imageDirectory.Set(GifImageDirectory.TagHeight, reader.GetUInt16()); var flags = reader.GetByte(); var hasColorTable = (flags >> 7) != 0; var isInterlaced = (flags & 0x40) != 0; imageDirectory.Set(GifImageDirectory.TagHasLocalColourTable, hasColorTable); imageDirectory.Set(GifImageDirectory.TagIsInterlaced, isInterlaced); if (hasColorTable) { var isColorTableSorted = (flags & 0x20) != 0; imageDirectory.Set(GifImageDirectory.TagIsColorTableSorted, isColorTableSorted); var bitsPerPixel = (flags & 0x7) + 1; imageDirectory.Set(GifImageDirectory.TagLocalColourTableBitsPerPixel, bitsPerPixel); // skip color table reader.Skip(3 * (2 << (flags & 0x7))); } // skip "LZW Minimum Code Size" byte reader.GetByte(); return imageDirectory; }
public static string Get4ccString([NotNull] this SequentialReader reader) { var sb = new StringBuilder(4); sb.Append((char)reader.GetByte()); sb.Append((char)reader.GetByte()); sb.Append((char)reader.GetByte()); sb.Append((char)reader.GetByte()); return(sb.ToString()); }
private static Directory?ReadGifExtensionBlock(SequentialReader reader) { var extensionLabel = reader.GetByte(); var blockSizeBytes = reader.GetByte(); var blockStartPos = reader.Position; Directory?directory; switch (extensionLabel) { case 0x01: { directory = ReadPlainTextBlock(reader, blockSizeBytes); break; } case 0xf9: { directory = ReadControlBlock(reader, blockSizeBytes); break; } case 0xfe: { directory = ReadCommentBlock(reader, blockSizeBytes); break; } case 0xff: { directory = ReadApplicationExtensionBlock(reader, blockSizeBytes); break; } default: { directory = new ErrorDirectory($"Unsupported GIF extension block with type 0x{extensionLabel:X2}."); break; } } var skipCount = blockStartPos + blockSizeBytes - reader.Position; if (skipCount > 0) { reader.Skip(skipCount); } return(directory); }
public ColorInformationBox(BoxLocation location, SequentialReader sr) : base(location) { ColorType = sr.GetUInt32(); switch (ColorType) { case NclxTag: { ColorPrimaries = sr.GetUInt16(); TransferCharacteristics = sr.GetUInt16(); MatrixCharacteristics = sr.GetUInt16(); FullRangeFlag = (sr.GetByte() & 128) == 128; IccProfile = _emptyByteArray; break; } case RICCTag: case ProfTag: { IccProfile = ReadRemainingData(sr); break; } default: { IccProfile = _emptyByteArray; break; } } }
public void Extract(SequentialReader reader, HuffmanTablesDirectory directory) { try { while (reader.Available() > 0) { byte header = reader.GetByte(); HuffmanTableClass tableClass = HuffmanTable.TypeOf((header & 0xF0) >> 4); int tableDestinationId = header & 0xF; byte[] lBytes = GetBytes(reader, 16); int vCount = 0; foreach (byte b in lBytes) { vCount += (b & 0xFF); } byte[] vBytes = GetBytes(reader, vCount); directory.AddTable(new HuffmanTable(tableClass, tableDestinationId, lBytes, vBytes)); } } catch (IOException me) { directory.AddError(me.ToString()); } }
private static GifControlDirectory ReadControlBlock(SequentialReader reader) { var directory = new GifControlDirectory(); byte packedFields = reader.GetByte(); directory.Set(GifControlDirectory.TagDisposalMethod, (packedFields >> 2) & 7); directory.Set(GifControlDirectory.TagUserInputFlag, (packedFields & 2) == 2); directory.Set(GifControlDirectory.TagTransparentColorFlag, (packedFields & 1) == 1); directory.Set(GifControlDirectory.TagDelay, reader.GetUInt16()); directory.Set(GifControlDirectory.TagTransparentColorIndex, reader.GetByte()); // skip 0x0 block terminator reader.Skip(1); return(directory); }
/// <summary> /// Main method that parses all comments and then distributes data extraction among other methods that parse the /// rest of file and store encountered data in metadata(if there exists an entry in EpsDirectory /// for the found data). Reads until a begin data/binary comment is found or reader's estimated /// available data has run out (or AI09 End Private Data). Will extract data from normal EPS comments, Photoshop, ICC, and XMP. /// </summary> /// <param name="directory"></param> /// <param name="directories">list to add directory to and extracted data</param> /// <param name="reader"></param> private void Extract(EpsDirectory directory, List <Directory> directories, SequentialReader reader) { var line = new StringBuilder(); while (true) { line.Length = 0; // Read the next line, excluding any trailing newline character // Note that for Windows-style line endings ("\r\n") the outer loop will be run a second time with an empty // string, which is fine. while (true) { char c = (char)reader.GetByte(); if (c == '\r' || c == '\n') { break; } line.Append(c); } // Stop when we hit a line that is not a comment if (line.Length != 0 && line[0] != '%') { break; } string name; // ':' signifies there is an associated keyword (should be put in directory) // otherwise, the name could be a marker int colonIndex = line.IndexOf(':'); if (colonIndex != -1) { name = line.ToString(0, colonIndex).Trim(); var value = line.ToString(colonIndex + 1, line.Length - (colonIndex + 1)).Trim(); AddToDirectory(directory, name, value); } else { name = line.ToString().Trim(); } // Some comments will both have a value and signify a new block to follow if (name.Equals("%BeginPhotoshop")) { ExtractPhotoshopData(directories, reader); } else if (name.Equals("%%BeginICCProfile")) { ExtractIccData(directories, reader); } else if (name.Equals("%begin_xml_packet")) { ExtractXmpData(directories, reader); } } }
private static byte[] GetBytes(SequentialReader reader, int count) { byte[] bytes = new byte[count]; for (int i = 0; i < count; i++) { byte b = reader.GetByte(); if (b == 0xFF) { byte stuffing = reader.GetByte(); if (stuffing != 0x00) { throw new MetadataException("Marker " + (JpegSegmentType)stuffing + " found inside DHT segment"); } } bytes[i] = b; } return(bytes); }
private static void SkipBlocks(SequentialReader reader) { while (true) { var length = reader.GetByte(); if (length == 0) return; reader.Skip(length); } }
private static Directory?ReadApplicationExtensionBlock(SequentialReader reader, byte blockSizeBytes) { if (blockSizeBytes != 11) { return(new ErrorDirectory($"Invalid GIF application extension block size. Expected 11, got {blockSizeBytes}.")); } var extensionType = reader.GetString(blockSizeBytes, Encoding.UTF8); switch (extensionType) { case "XMP DataXMP": { // XMP data extension var xmpBytes = GatherBytes(reader); int xmpLength = xmpBytes.Length - 257; // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF // Only extract valid blocks return(xmpLength > 0 ? new XmpReader().Extract(xmpBytes, 0, xmpBytes.Length - 257) : null); } case "ICCRGBG1012": { // ICC profile extension var iccBytes = GatherBytes(reader, reader.GetByte()); return(iccBytes.Length != 0 ? new IccReader().Extract(new ByteArrayReader(iccBytes)) : null); } case "NETSCAPE2.0": { reader.Skip(2); // Netscape's animated GIF extension // Iteration count (0 means infinite) var iterationCount = reader.GetUInt16(); // Skip terminator reader.Skip(1); var animationDirectory = new GifAnimationDirectory(); animationDirectory.Set(GifAnimationDirectory.TagIterationCount, iterationCount); return(animationDirectory); } default: { SkipBlocks(reader); return(null); } } }
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(); }
private const uint UriTag = 0x75726920; // fdel public ItemInfoEntryBox(BoxLocation location, SequentialReader reader) : base(location, reader) { if (Version <= 1) { ItemId = reader.GetUInt16(); ItemProtectionIndex = reader.GetUInt16(); ItemName = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); } if (Version == 1) { ExtensionType = reader.GetUInt32(); if (ExtensionType == FdelTag) { Location = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); MD5 = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); ContentLength = reader.GetUInt64(); TransferLength = reader.GetUInt64(); GroupIdCount = reader.GetByte(); GroupIds = new uint[GroupIdCount]; for (int i = 0; i < GroupIdCount; i++) { GroupIds[i] = reader.GetUInt32(); } } } if (Version >= 2) { ItemId = Version == 2 ? reader.GetUInt16() : reader.GetUInt32(); ItemProtectionIndex = reader.GetUInt16(); ItemType = reader.GetUInt32(); ItemName = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); if (ItemType == MimeTag) { ContentType = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); ContentEncoding = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); } if (ItemType == UriTag) { ItemUri = reader.GetNullTerminatedString((int)reader.BytesRemainingInBox(location), Encoding.UTF8); } } }
/// <summary> /// Reads all bytes until the given sentinel is observed. /// The sentinel will be included in the returned bytes. /// </summary> private static byte[] ReadUntil(SequentialReader reader, byte[] sentinel) { var bytes = new MemoryStream(); int length = sentinel.Length; int depth = 0; while (depth != length) { byte b = reader.GetByte(); if (b == sentinel[depth]) { depth++; } else { depth = 0; } bytes.WriteByte(b); } return(bytes.ToArray()); }
private static GifControlDirectory ReadControlBlock(SequentialReader reader, byte blockSizeBytes) { if (blockSizeBytes < 4) { blockSizeBytes = 4; } var directory = new GifControlDirectory(); reader.Skip(1); directory.Set(GifControlDirectory.TagDelay, reader.GetUInt16()); if (blockSizeBytes > 3) { reader.Skip(blockSizeBytes - 3); } // skip 0x0 block terminator reader.GetByte(); return(directory); }
/// <summary>Reads IPTC values and returns them in an <see cref="IptcDirectory"/>.</summary> /// <remarks> /// Note that IPTC data does not describe its own length, hence <paramref name="length"/> is required. /// </remarks> public IptcDirectory Extract(SequentialReader reader, long length) { var directory = new IptcDirectory(); var offset = 0; // for each tag while (offset < length) { // identifies start of a tag byte startByte; try { startByte = reader.GetByte(); offset++; } catch (IOException) { directory.AddError("Unable to read starting byte of IPTC tag"); break; } if (startByte != IptcMarkerByte) { // NOTE have seen images where there was one extra byte at the end, giving // offset==length at this point, which is not worth logging as an error. if (offset != length) { directory.AddError($"Invalid IPTC tag marker at offset {offset - 1}. Expected '0x{IptcMarkerByte:x2}' but got '0x{startByte:x}'."); } break; } // we need at least four bytes left to read a tag if (offset + 4 > length) { directory.AddError("Too few bytes remain for a valid IPTC tag"); break; } int directoryType; int tagType; int tagByteCount; try { directoryType = reader.GetByte(); tagType = reader.GetByte(); tagByteCount = reader.GetUInt16(); if (tagByteCount > 0x7FFF) { // Extended DataSet Tag (see 1.5(c), p14, IPTC-IIMV4.2.pdf) tagByteCount = ((tagByteCount & 0x7FFF) << 16) | reader.GetUInt16(); offset += 2; } offset += 4; } catch (IOException) { directory.AddError("IPTC data segment ended mid-way through tag descriptor"); break; } if (offset + tagByteCount > length) { directory.AddError("Data for tag extends beyond end of IPTC segment"); break; } try { ProcessTag(reader, directory, directoryType, tagType, tagByteCount); } catch (IOException) { directory.AddError("Error processing IPTC tag"); break; } offset += tagByteCount; } return(directory); }
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 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); }
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); }
/// <summary>This method exists because generator methods cannot yield in try/catch blocks.</summary> private static IEnumerable<Directory> ReadGifDirectoriesInternal(SequentialReader reader) { var header = ReadGifHeader(reader); yield return header; if (header.HasError) yield break; if (header.TryGetBoolean(GifHeaderDirectory.TagHasGlobalColorTable, out bool hasGlobalColorTable)) { // Skip over any global colour table if (hasGlobalColorTable && header.TryGetInt32(GifHeaderDirectory.TagColorTableSize, out int globalColorTableSize)) { // Colour table has R/G/B byte triplets reader.Skip(3 * globalColorTableSize); } } else { yield return new ErrorDirectory("GIF did not have hasGlobalColorTable bit."); } // After the header comes a sequence of blocks while (true) { byte marker; try { marker = reader.GetByte(); } catch (IOException) { yield break; } switch (marker) { case (byte)'!': // 0x21 { var extBlock = ReadGifExtensionBlock(reader); if (extBlock != null) yield return extBlock; break; } case (byte)',': // 0x2c { var imageBlock = ReadImageBlock(reader); if (imageBlock != null) yield return imageBlock; // skip image data blocks SkipBlocks(reader); break; } case (byte)';': // 0x3b { // terminator yield break; } default: { // Anything other than these types is unexpected. // GIF87a spec says to keep reading until a separator is found. // GIF89a spec says file is corrupt. yield return new ErrorDirectory("Unknown GIF block marker found."); yield break; } } } }
public PixelInformationBox(BoxLocation location, SequentialReader reader) : base(location, reader) { ChannelCount = reader.GetByte(); BitsPerChannel = reader.GetBytes(ChannelCount); }
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); }
} // rotation is anti-clockwise and valid values are 0,90,180, and 270 public ImageRotationBox(BoxLocation boxLocation, SequentialReader sr) : base(boxLocation) { Rotation = (uint)((sr.GetByte() & 3) * 90); }
/// <summary>Reads the fixed-position GIF header.</summary> private static GifHeaderDirectory ReadGifHeader(SequentialReader reader) { // FILE HEADER // // 3 - signature: "GIF" // 3 - version: either "87a" or "89a" // // LOGICAL SCREEN DESCRIPTOR // // 2 - pixel width // 2 - pixel height // 1 - screen and color map information flags (0 is LSB) // 0-2 Size of the global color table // 3 Color table sort flag (89a only) // 4-6 Color resolution // 7 Global color table flag // 1 - background color index // 1 - pixel aspect ratio var headerDirectory = new GifHeaderDirectory(); var signature = reader.GetString(3, Encoding.UTF8); if (signature != "GIF") { headerDirectory.AddError("Invalid GIF file signature"); return headerDirectory; } var version = reader.GetString(3, Encoding.UTF8); if (version != Gif87AVersionIdentifier && version != Gif89AVersionIdentifier) { headerDirectory.AddError($"Unexpected GIF version \"{version}\""); return headerDirectory; } headerDirectory.Set(GifHeaderDirectory.TagGifFormatVersion, version); // LOGICAL SCREEN DESCRIPTOR headerDirectory.Set(GifHeaderDirectory.TagImageWidth, reader.GetUInt16()); headerDirectory.Set(GifHeaderDirectory.TagImageHeight, reader.GetUInt16()); var flags = reader.GetByte(); // First three bits = (BPP - 1) var colorTableSize = 1 << ((flags & 7) + 1); var bitsPerPixel = ((flags & 0x70) >> 4) + 1; var hasGlobalColorTable = (flags >> 7) != 0; headerDirectory.Set(GifHeaderDirectory.TagColorTableSize, colorTableSize); if (version == Gif89AVersionIdentifier) { var isColorTableSorted = (flags & 8) != 0; headerDirectory.Set(GifHeaderDirectory.TagIsColorTableSorted, isColorTableSorted); } headerDirectory.Set(GifHeaderDirectory.TagBitsPerPixel, bitsPerPixel); headerDirectory.Set(GifHeaderDirectory.TagHasGlobalColorTable, hasGlobalColorTable); headerDirectory.Set(GifHeaderDirectory.TagBackgroundColorIndex, reader.GetByte()); int aspectRatioByte = reader.GetByte(); if (aspectRatioByte != 0) { var pixelAspectRatio = (float)((aspectRatioByte + 15d) / 64d); headerDirectory.Set(GifHeaderDirectory.TagPixelAspectRatio, pixelAspectRatio); } return headerDirectory; }
/** * EPS files can contain hexadecimal-encoded ASCII blocks, each prefixed with <c>"% "</c>. * This method reads such a block and returns a byte[] of the decoded contents. * Reading stops at the first invalid line, which is discarded (it's a terminator anyway). * <p/> * For example: * <pre><code> * %BeginPhotoshop: 9564 * % 3842494D040400000000005D1C015A00031B25471C0200000200041C02780004 * % 6E756C6C1C027A00046E756C6C1C025000046E756C6C1C023700083230313630 * % 3331311C023C000B3131343335362B303030301C023E00083230313630333131 * % 48000000010000003842494D03FD0000000000080101000000000000 * %EndPhotoshop * </code></pre> * When calling this method, the reader must be positioned at the start of the first line containing * hex data, not at the introductory line. * * @return The decoded bytes, or <code>null</code> if decoding failed. */ /// <remarks> /// EPS files can contain hexadecimal-encoded ASCII blocks, each prefixed with "% ". /// This method reads such a block and returns a byte[] of the decoded contents. /// Reading stops at the first invalid line, which is discarded(it's a terminator anyway). /// <para /> /// For example: /// <para /> /// %BeginPhotoshop: 9564 /// % 3842494D040400000000005D1C015A00031B25471C0200000200041C02780004 /// % 6E756C6C1C027A00046E756C6C1C025000046E756C6C1C023700083230313630 /// % 3331311C023C000B3131343335362B303030301C023E00083230313630333131 /// % 48000000010000003842494D03FD0000000000080101000000000000 /// %EndPhotoshop /// <para /> /// When calling this method, the reader must be positioned at the start of the first line containing /// hex data, not at the introductory line. /// </remarks> /// <returns>The decoded bytes, or null if decoding failed.</returns> private static byte[]? DecodeHexCommentBlock(SequentialReader reader) { var bytes = new MemoryStream(); // Use a state machine to efficiently parse data in a single traversal const int AwaitingPercent = 0; const int AwaitingSpace = 1; const int AwaitingHex1 = 2; const int AwaitingHex2 = 3; int state = AwaitingPercent; int carry = 0; bool done = false; byte b = 0; while (!done) { b = reader.GetByte(); switch (state) { case AwaitingPercent: { switch (b) { case (byte)'\r': case (byte)'\n': case (byte)' ': // skip newline chars and spaces break; case (byte)'%': state = AwaitingSpace; break; default: return(null); } break; } case AwaitingSpace: { switch (b) { case (byte)' ': state = AwaitingHex1; break; default: done = true; break; } break; } case AwaitingHex1: { int i = TryHexToInt(b); if (i != -1) { carry = i * 16; state = AwaitingHex2; } else if (b == '\r' || b == '\n') { state = AwaitingPercent; } else { return(null); } break; } case AwaitingHex2: { int i = TryHexToInt(b); if (i == -1) { return(null); } bytes.WriteByte((byte)(carry + i)); state = AwaitingHex1; break; } } } // skip through the remainder of the last line while (b != '\n') { b = reader.GetByte(); } return(bytes.ToArray()); }
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 static decimal Get16BitFixedPoint([NotNull] this SequentialReader reader) { return(decimal.Add( reader.GetByte(), decimal.Divide(reader.GetByte(), byte.MaxValue))); }
public DirectoryList Extract([NotNull] SequentialReader reader) { var directories = new List <Directory>(); reader = reader.WithByteOrder(isMotorolaByteOrder: false); var type = 0; var imageCount = 0; // Read header (ICONDIR structure) string error = null; try { var reserved = reader.GetUInt16(); type = reader.GetUInt16(); imageCount = reader.GetUInt16(); if (reserved != 0) { error = "Invalid header bytes"; } else if (type != 1 && type != 2) { error = "Invalid type " + type + " -- expecting 1 or 2"; } else if (imageCount == 0) { error = "Image count cannot be zero"; } } catch (IOException ex) { error = "Exception reading ICO file metadata: " + ex.Message; } if (error != null) { var directory = new IcoDirectory(); directory.AddError(error); directories.Add(directory); return(directories); } // Read each embedded image for (var imageIndex = 0; imageIndex < imageCount; imageIndex++) { var directory = new IcoDirectory(); try { directory.Set(IcoDirectory.TagImageType, type); directory.Set(IcoDirectory.TagImageWidth, reader.GetByte()); directory.Set(IcoDirectory.TagImageHeight, reader.GetByte()); directory.Set(IcoDirectory.TagColourPaletteSize, reader.GetByte()); // Ignore this byte (normally zero, though .NET's System.Drawing.Icon.Save method writes 255) reader.GetByte(); if (type == 1) { // Icon directory.Set(IcoDirectory.TagColourPlanes, reader.GetUInt16()); directory.Set(IcoDirectory.TagBitsPerPixel, reader.GetUInt16()); } else { // Cursor directory.Set(IcoDirectory.TagCursorHotspotX, reader.GetUInt16()); directory.Set(IcoDirectory.TagCursorHotspotY, reader.GetUInt16()); } directory.Set(IcoDirectory.TagImageSizeBytes, reader.GetUInt32()); directory.Set(IcoDirectory.TagImageOffsetBytes, reader.GetUInt32()); } catch (IOException ex) { directory.AddError("Exception reading ICO file metadata: " + ex.Message); } directories.Add(directory); if (directory.HasError) { break; } } return(directories); }
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); }