Beispiel #1
0
        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;
        }
Beispiel #3
0
        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());
        }
Beispiel #4
0
        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;
            }
            }
        }
Beispiel #6
0
        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);
        }
Beispiel #8
0
        /// <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);
                }
            }
        }
Beispiel #9
0
 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);
            }
        }
Beispiel #11
0
        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);
                }
            }
        }
Beispiel #15
0
        /// <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());
        }
Beispiel #16
0
        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);
        }
Beispiel #24
0
        }                             // 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;
        }
Beispiel #26
0
        /**
         * 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);
            }
        }
Beispiel #28
0
 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);
        }
Beispiel #30
0
        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);
        }