Exemple #1
0
        /// <summary>Processes a RIFF data sequence.</summary>
        /// <param name="reader">
        /// the
        /// <see cref="Com.Drew.Lang.SequentialReader"/>
        /// from which the data should be read
        /// </param>
        /// <param name="handler">
        /// the
        /// <see cref="RiffHandler"/>
        /// that will coordinate processing and accept read values
        /// </param>
        /// <exception cref="RiffProcessingException">
        /// if an error occurred during the processing of RIFF data that could not be
        /// ignored or recovered from
        /// </exception>
        /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception>
        /// <exception cref="Com.Drew.Imaging.Riff.RiffProcessingException"/>
        public virtual void ProcessRiff([NotNull] SequentialReader reader, [NotNull] RiffHandler handler)
        {
            // RIFF files are always little-endian
            reader.SetMotorolaByteOrder(false);
            // PROCESS FILE HEADER
            string fileFourCC = reader.GetString(4);

            if (!fileFourCC.Equals("RIFF"))
            {
                throw new RiffProcessingException("Invalid RIFF header: " + fileFourCC);
            }
            // The total size of the chunks that follow plus 4 bytes for the 'WEBP' FourCC
            int    fileSize   = reader.GetInt32();
            int    sizeLeft   = fileSize;
            string identifier = reader.GetString(4);

            sizeLeft -= 4;
            if (!handler.ShouldAcceptRiffIdentifier(identifier))
            {
                return;
            }
            // PROCESS CHUNKS
            while (sizeLeft != 0)
            {
                string chunkFourCC = reader.GetString(4);
                int    chunkSize   = reader.GetInt32();
                sizeLeft -= 8;
                // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as Java cannot
                // allocate arrays larger than this.
                if (chunkSize < 0 || sizeLeft < chunkSize)
                {
                    throw new RiffProcessingException("Invalid RIFF chunk size");
                }
                if (handler.ShouldAcceptChunk(chunkFourCC))
                {
                    // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler?
                    handler.ProcessChunk(chunkFourCC, reader.GetBytes(chunkSize));
                }
                else
                {
                    reader.Skip(chunkSize);
                }
                sizeLeft -= chunkSize;
                // Skip any padding byte added to keep chunks aligned to even numbers of bytes
                if (chunkSize % 2 == 1)
                {
                    reader.GetInt8();
                    sizeLeft--;
                }
            }
        }
Exemple #2
0
        /// <summary>Processes a RIFF data sequence.</summary>
        /// <param name="reader">The <see cref="SequentialReader"/> from which the data should be read.</param>
        /// <param name="handler">The <see cref="IRiffHandler"/> that will coordinate processing and accept read values.</param>
        /// <exception cref="RiffProcessingException">An error occurred during the processing of RIFF data that could not be ignored or recovered from.</exception>
        /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception>
        public void ProcessRiff([NotNull] SequentialReader reader, [NotNull] IRiffHandler handler)
        {
            // RIFF files are always little-endian
            reader = reader.WithByteOrder(isMotorolaByteOrder: false);

            // PROCESS FILE HEADER

            var fileFourCc = reader.GetString(4, Encoding.ASCII);

            if (fileFourCc != "RIFF")
            {
                throw new RiffProcessingException("Invalid RIFF header: " + fileFourCc);
            }

            // The total size of the chunks that follow plus 4 bytes for the 'WEBP' or 'AVI ' FourCC
            int    fileSize   = reader.GetInt32();
            int    sizeLeft   = fileSize;
            string identifier = reader.GetString(4, Encoding.ASCII);

            sizeLeft -= 4;

            if (!handler.ShouldAcceptRiffIdentifier(identifier))
            {
                return;
            }

            ProcessChunks(reader, sizeLeft, handler);
        }
Exemple #3
0
        // PROCESS CHUNKS
        private static void ProcessChunks(SequentialReader reader, long maxPosition, IRiffHandler handler)
        {
            // Processing chunks. Each chunk is 8 bytes header (4 bytes CC code + 4 bytes length of chunk) + data of the chunk

            while (reader.Position < maxPosition - 8)
            {
                string chunkFourCc = reader.GetString(4, Encoding.ASCII);
                int    chunkSize   = reader.GetInt32();

                // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this
                if (chunkSize < 0 || chunkSize + reader.Position > maxPosition)
                {
                    throw new RiffProcessingException("Invalid RIFF chunk size");
                }

                if (chunkFourCc == "LIST" || chunkFourCc == "RIFF")
                {
                    string listName = reader.GetString(4, Encoding.ASCII);
                    if (handler.ShouldAcceptList(listName))
                    {
                        ProcessChunks(reader, reader.Position + chunkSize - 4, handler);
                    }
                    else
                    {
                        reader.Skip(chunkSize - 4);
                    }
                }
                else
                {
                    if (handler.ShouldAcceptChunk(chunkFourCc))
                    {
                        // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler?
                        handler.ProcessChunk(chunkFourCc, reader.GetBytes(chunkSize));
                    }
                    else
                    {
                        reader.Skip(chunkSize);
                    }

                    // Skip any padding byte added to keep chunks aligned to even numbers of bytes
                    if (chunkSize % 2 == 1)
                    {
                        reader.Skip(1);
                    }
                }
            }
        }
 static TgaTagInfo GetTag(SequentialReader reader)
 {
     return(new TgaTagInfo(reader.GetInt16(), reader.GetInt32(), reader.GetInt32()));
 }
Exemple #5
0
        public virtual void Extract([NotNull] SequentialReader reader, [NotNull] Com.Drew.Metadata.Metadata metadata)
        {
            PsdHeaderDirectory directory = new PsdHeaderDirectory();

            metadata.AddDirectory(directory);
            // FILE HEADER SECTION
            try
            {
                int signature = reader.GetInt32();
                if (signature != unchecked ((int)(0x38425053)))
                {
                    // "8BPS"
                    directory.AddError("Invalid PSD file signature");
                    return;
                }
                int version = reader.GetUInt16();
                if (version != 1 && version != 2)
                {
                    directory.AddError("Invalid PSD file version (must be 1 or 2)");
                    return;
                }
                // 6 reserved bytes are skipped here.  They should be zero.
                reader.Skip(6);
                int channelCount = reader.GetUInt16();
                directory.SetInt(PsdHeaderDirectory.TagChannelCount, channelCount);
                // even though this is probably an unsigned int, the max height in practice is 300,000
                int imageHeight = reader.GetInt32();
                directory.SetInt(PsdHeaderDirectory.TagImageHeight, imageHeight);
                // even though this is probably an unsigned int, the max width in practice is 300,000
                int imageWidth = reader.GetInt32();
                directory.SetInt(PsdHeaderDirectory.TagImageWidth, imageWidth);
                int bitsPerChannel = reader.GetUInt16();
                directory.SetInt(PsdHeaderDirectory.TagBitsPerChannel, bitsPerChannel);
                int colorMode = reader.GetUInt16();
                directory.SetInt(PsdHeaderDirectory.TagColorMode, colorMode);
            }
            catch (IOException)
            {
                directory.AddError("Unable to read PSD header");
                return;
            }
            // COLOR MODE DATA SECTION
            try
            {
                long sectionLength = reader.GetUInt32();

                /*
                 * Only indexed color and duotone (see the mode field in the File header section) have color mode data.
                 * For all other modes, this section is just the 4-byte length field, which is set to zero.
                 *
                 * Indexed color images: length is 768; color data contains the color table for the image,
                 *                       in non-interleaved order.
                 * Duotone images: color data contains the duotone specification (the format of which is not documented).
                 *                 Other applications that read Photoshop files can treat a duotone image as a gray	image,
                 *                 and just preserve the contents of the duotone information when reading and writing the
                 *                 file.
                 */
                reader.Skip(sectionLength);
            }
            catch (IOException)
            {
                return;
            }
            // IMAGE RESOURCES SECTION
            try
            {
                long sectionLength = reader.GetUInt32();
                System.Diagnostics.Debug.Assert((sectionLength <= int.MaxValue));
                new PhotoshopReader().Extract(reader, (int)sectionLength, metadata);
            }
            catch (IOException)
            {
            }
        }
        public IEnumerable <PngChunk> Extract([NotNull] SequentialReader reader, [CanBeNull] ICollection <PngChunkType> desiredChunkTypes)
        {
            //
            // PNG DATA STREAM
            //
            // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS.
            //
            // PNG SIGNATURE
            //
            //   Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A
            //
            // CHUNK
            //
            //   4 - length of the data field (unsigned, but always within 31 bytes), may be zero
            //   4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z)
            //   * - data field
            //   4 - CRC calculated from chunk type and chunk data, but not length
            //
            // CHUNK TYPES
            //
            //   Critical Chunk Types:
            //
            //     IHDR - image header, always the first chunk in the data stream
            //     PLTE - palette table, associated with indexed PNG images
            //     IDAT - image data chunk, of which there may be many
            //     IEND - image trailer, always the last chunk in the data stream
            //
            //   Ancillary Chunk Types:
            //
            //     Transparency information:  tRNS
            //     Colour space information:  cHRM, gAMA, iCCP, sBIT, sRGB
            //     Textual information:       iTXt, tEXt, zTXt
            //     Miscellaneous information: bKGD, hIST, pHYs, sPLT
            //     Time information:          tIME
            //

            // network byte order
            reader = reader.WithByteOrder(isMotorolaByteOrder: true);

            if (!_pngSignatureBytes.SequenceEqual(reader.GetBytes(_pngSignatureBytes.Length)))
            {
                throw new PngProcessingException("PNG signature mismatch");
            }

            var seenImageHeader  = false;
            var seenImageTrailer = false;
            var chunks           = new List <PngChunk>();
            var seenChunkTypes   = new HashSet <PngChunkType>();

            while (!seenImageTrailer)
            {
                // Process the next chunk.
                var chunkDataLength = reader.GetInt32();
                if (chunkDataLength < 0)
                {
                    throw new PngProcessingException("PNG chunk length exceeds maximum");
                }
                var chunkType      = new PngChunkType(reader.GetBytes(4));
                var willStoreChunk = desiredChunkTypes == null || desiredChunkTypes.Contains(chunkType);
                var chunkData      = reader.GetBytes(chunkDataLength);

                // Skip the CRC bytes at the end of the chunk
                // TODO consider verifying the CRC value to determine if we're processing bad data
                reader.Skip(4);

                if (willStoreChunk && seenChunkTypes.Contains(chunkType) && !chunkType.AreMultipleAllowed)
                {
                    throw new PngProcessingException($"Observed multiple instances of PNG chunk '{chunkType}', for which multiples are not allowed");
                }

                if (chunkType.Equals(PngChunkType.IHDR))
                {
                    seenImageHeader = true;
                }
                else if (!seenImageHeader)
                {
                    throw new PngProcessingException($"First chunk should be '{PngChunkType.IHDR}', but '{chunkType}' was observed");
                }

                if (chunkType.Equals(PngChunkType.IEND))
                {
                    seenImageTrailer = true;
                }

                if (willStoreChunk)
                {
                    chunks.Add(new PngChunk(chunkType, chunkData));
                }

                seenChunkTypes.Add(chunkType);
            }

            return(chunks);
        }
Exemple #7
0
        /// <summary>Processes a RIFF data sequence.</summary>
        /// <param name="reader">The <see cref="SequentialReader"/> from which the data should be read.</param>
        /// <param name="handler">The <see cref="IRiffHandler"/> that will coordinate processing and accept read values.</param>
        /// <exception cref="RiffProcessingException">An error occurred during the processing of RIFF data that could not be ignored or recovered from.</exception>
        /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception>
        public void ProcessRiff([NotNull] SequentialReader reader, [NotNull] IRiffHandler handler)
        {
            // RIFF files are always little-endian
            reader = reader.WithByteOrder(isMotorolaByteOrder: false);

            // PROCESS FILE HEADER

            var fileFourCc = reader.GetString(4, Encoding.UTF8);

            if (fileFourCc != "RIFF")
            {
                throw new RiffProcessingException("Invalid RIFF header: " + fileFourCc);
            }

            // The total size of the chunks that follow plus 4 bytes for the 'WEBP' FourCC
            var fileSize   = reader.GetInt32();
            var sizeLeft   = fileSize;
            var identifier = reader.GetString(4, Encoding.UTF8);

            sizeLeft -= 4;

            if (!handler.ShouldAcceptRiffIdentifier(identifier))
            {
                return;
            }

            // PROCESS CHUNKS

            while (sizeLeft != 0)
            {
                var chunkFourCc = reader.GetString(4, Encoding.UTF8);
                var chunkSize   = reader.GetInt32();

                sizeLeft -= 8;

                // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this
                if (chunkSize < 0 || sizeLeft < chunkSize)
                {
                    throw new RiffProcessingException("Invalid RIFF chunk size");
                }

                if (handler.ShouldAcceptChunk(chunkFourCc))
                {
                    // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler?
                    handler.ProcessChunk(chunkFourCc, reader.GetBytes(chunkSize));
                }
                else
                {
                    reader.Skip(chunkSize);
                }

                sizeLeft -= chunkSize;

                // Skip any padding byte added to keep chunks aligned to even numbers of bytes
                if (chunkSize % 2 == 1)
                {
                    reader.GetSByte();
                    sizeLeft--;
                }
            }
        }
Exemple #8
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);
        }
Exemple #9
0
        public virtual void Extract(SequentialReader reader, Com.Drew.Metadata.Metadata metadata)
        {
            BmpHeaderDirectory directory = metadata.GetOrCreateDirectory <BmpHeaderDirectory>();

            // FILE HEADER
            //
            // 2 - magic number (0x42 0x4D = "BM")
            // 4 - size of BMP file in bytes
            // 2 - reserved
            // 2 - reserved
            // 4 - the offset of the pixel array
            //
            // BITMAP INFORMATION HEADER
            //
            // The first four bytes of the header give the size, which is a discriminator of the actual header format.
            // See this for more information http://en.wikipedia.org/wiki/BMP_file_format
            //
            // BITMAPINFOHEADER (size = 40)
            //
            // 4 - size of header
            // 4 - pixel width (signed)
            // 4 - pixel height (signed)
            // 2 - number of colour planes (must be set to 1)
            // 2 - number of bits per pixel
            // 4 - compression being used (needs decoding)
            // 4 - pixel data length (not total file size, just pixel array)
            // 4 - horizontal resolution, pixels/meter (signed)
            // 4 - vertical resolution, pixels/meter (signed)
            // 4 - number of colours in the palette (0 means no palette)
            // 4 - number of important colours (generally ignored)
            //
            // BITMAPCOREHEADER (size = 12)
            //
            // 4 - size of header
            // 2 - pixel width
            // 2 - pixel height
            // 2 - number of colour planes (must be set to 1)
            // 2 - number of bits per pixel
            //
            // COMPRESSION VALUES
            //
            // 0 = None
            // 1 = RLE 8-bit/pixel
            // 2 = RLE 4-bit/pixel
            // 3 = Bit field (or Huffman 1D if BITMAPCOREHEADER2 (size 64))
            // 4 = JPEG (or RLE-24 if BITMAPCOREHEADER2 (size 64))
            // 5 = PNG
            // 6 = Bit field
            reader.SetMotorolaByteOrder(false);
            try
            {
                int magicNumber = reader.GetUInt16();
                if (magicNumber != unchecked ((int)(0x4D42)))
                {
                    directory.AddError("Invalid BMP magic number");
                    return;
                }
                // skip past the rest of the file header
                reader.Skip(4 + 2 + 2 + 4);
                int headerSize = reader.GetInt32();
                directory.SetInt(BmpHeaderDirectory.TagHeaderSize, headerSize);
                // We expect the header size to be either 40 (BITMAPINFOHEADER) or 12 (BITMAPCOREHEADER)
                if (headerSize == 40)
                {
                    // BITMAPINFOHEADER
                    directory.SetInt(BmpHeaderDirectory.TagImageWidth, reader.GetInt32());
                    directory.SetInt(BmpHeaderDirectory.TagImageHeight, reader.GetInt32());
                    directory.SetInt(BmpHeaderDirectory.TagColourPlanes, reader.GetInt16());
                    directory.SetInt(BmpHeaderDirectory.TagBitsPerPixel, reader.GetInt16());
                    directory.SetInt(BmpHeaderDirectory.TagCompression, reader.GetInt32());
                    // skip the pixel data length
                    reader.Skip(4);
                    directory.SetInt(BmpHeaderDirectory.TagXPixelsPerMeter, reader.GetInt32());
                    directory.SetInt(BmpHeaderDirectory.TagYPixelsPerMeter, reader.GetInt32());
                    directory.SetInt(BmpHeaderDirectory.TagPaletteColourCount, reader.GetInt32());
                    directory.SetInt(BmpHeaderDirectory.TagImportantColourCount, reader.GetInt32());
                }
                else
                {
                    if (headerSize == 12)
                    {
                        directory.SetInt(BmpHeaderDirectory.TagImageWidth, reader.GetInt16());
                        directory.SetInt(BmpHeaderDirectory.TagImageHeight, reader.GetInt16());
                        directory.SetInt(BmpHeaderDirectory.TagColourPlanes, reader.GetInt16());
                        directory.SetInt(BmpHeaderDirectory.TagBitsPerPixel, reader.GetInt16());
                    }
                    else
                    {
                        directory.AddError("Unexpected DIB header size: " + headerSize);
                    }
                }
            }
            catch (IOException)
            {
                directory.AddError("Unable to read BMP header");
            }
        }
Exemple #10
0
        /// <exception cref="Com.Drew.Imaging.Png.PngProcessingException"/>
        /// <exception cref="System.IO.IOException"/>
        public virtual Iterable <PngChunk> Extract(SequentialReader reader, ICollection <PngChunkType> desiredChunkTypes)
        {
            //
            // PNG DATA STREAM
            //
            // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS.
            //
            // PNG SIGNATURE
            //
            //   Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A
            //
            // CHUNK
            //
            //   4 - length of the data field (unsigned, but always within 31 bytes), may be zero
            //   4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z)
            //   * - data field
            //   4 - CRC calculated from chunk type and chunk data, but not length
            //
            // CHUNK TYPES
            //
            //   Critical Chunk Types:
            //
            //     IHDR - image header, always the first chunk in the data stream
            //     PLTE - palette table, associated with indexed PNG images
            //     IDAT - image data chunk, of which there may be many
            //     IEND - image trailer, always the last chunk in the data stream
            //
            //   Ancillary Chunk Types:
            //
            //     Transparency information:  tRNS
            //     Colour space information:  cHRM, gAMA, iCCP, sBIT, sRGB
            //     Textual information:       iTXt, tEXt, zTXt
            //     Miscellaneous information: bKGD, hIST, pHYs, sPLT
            //     Time information:          tIME
            //
            reader.SetMotorolaByteOrder(true);
            // network byte order
            if (!Arrays.Equals(PngSignatureBytes, reader.GetBytes(PngSignatureBytes.Length)))
            {
                throw new PngProcessingException("PNG signature mismatch");
            }
            bool                       seenImageHeader  = false;
            bool                       seenImageTrailer = false;
            IList <PngChunk>           chunks           = new AList <PngChunk>();
            ICollection <PngChunkType> seenChunkTypes   = new HashSet <PngChunkType>();

            while (!seenImageTrailer)
            {
                // Process the next chunk.
                int          chunkDataLength = reader.GetInt32();
                PngChunkType chunkType       = new PngChunkType(reader.GetBytes(4));
                sbyte[]      chunkData       = reader.GetBytes(chunkDataLength);
                // Skip the CRC bytes at the end of the chunk
                // TODO consider verifying the CRC value to determine if we're processing bad data
                reader.Skip(4);
                if (seenChunkTypes.Contains(chunkType) && !chunkType.AreMultipleAllowed())
                {
                    throw new PngProcessingException(Sharpen.Extensions.StringFormat("Observed multiple instances of PNG chunk '%s', for which multiples are not allowed", chunkType));
                }
                if (chunkType.Equals(PngChunkType.Ihdr))
                {
                    seenImageHeader = true;
                }
                else
                {
                    if (!seenImageHeader)
                    {
                        throw new PngProcessingException(Sharpen.Extensions.StringFormat("First chunk should be '%s', but '%s' was observed", PngChunkType.Ihdr, chunkType));
                    }
                }
                if (chunkType.Equals(PngChunkType.Iend))
                {
                    seenImageTrailer = true;
                }
                if (desiredChunkTypes == null || desiredChunkTypes.Contains(chunkType))
                {
                    chunks.Add(new PngChunk(chunkType, chunkData));
                }
                seenChunkTypes.Add(chunkType);
            }
            return(chunks.AsIterable());
        }
        public DirectoryList Extract(SequentialReader reader)
        {
            var directory = new PsdHeaderDirectory();

            // FILE HEADER SECTION

            try
            {
                var signature = reader.GetInt32();
                var version   = reader.GetUInt16();

                if (signature != 0x38425053)
                {
                    // "8BPS"
                    directory.AddError("Invalid PSD file signature");
                }
                else if (version != 1 && version != 2)
                {
                    directory.AddError("Invalid PSD file version (must be 1 or 2)");
                }
                else
                {
                    // 6 reserved bytes are skipped here.  They should be zero.
                    reader.Skip(6);
                    var channelCount = reader.GetUInt16();
                    directory.Set(PsdHeaderDirectory.TagChannelCount, channelCount);
                    // even though this is probably an unsigned int, the max height in practice is 300,000
                    var imageHeight = reader.GetInt32();
                    directory.Set(PsdHeaderDirectory.TagImageHeight, imageHeight);
                    // even though this is probably an unsigned int, the max width in practice is 300,000
                    var imageWidth = reader.GetInt32();
                    directory.Set(PsdHeaderDirectory.TagImageWidth, imageWidth);
                    var bitsPerChannel = reader.GetUInt16();
                    directory.Set(PsdHeaderDirectory.TagBitsPerChannel, bitsPerChannel);
                    var colorMode = reader.GetUInt16();
                    directory.Set(PsdHeaderDirectory.TagColorMode, colorMode);
                }
            }
            catch (IOException)
            {
                directory.AddError("Unable to read PSD header");
            }

            if (directory.HasError)
            {
                return new Directory[] { directory }
            }
            ;

            IEnumerable <Directory>?photoshopDirectories = null;

            try
            {
                // COLOR MODE DATA SECTION

                // Only indexed color and duotone (see the mode field in the File header section) have color mode data.
                // For all other modes, this section is just the 4-byte length field, which is set to zero.
                //
                // Indexed color images: length is 768; color data contains the color table for the image,
                //                       in non-interleaved order.
                // Duotone images: color data contains the duotone specification (the format of which is not documented).
                //                 Other applications that read Photoshop files can treat a duotone image as a gray    image,
                //                 and just preserve the contents of the duotone information when reading and writing the
                //                 file.
                var colorModeSectionLength = reader.GetUInt32();

                reader.Skip(colorModeSectionLength);

                // IMAGE RESOURCES SECTION

                var imageResourcesSectionLength = reader.GetUInt32();
                Debug.Assert(imageResourcesSectionLength <= int.MaxValue);
                photoshopDirectories = new PhotoshopReader().Extract(reader, (int)imageResourcesSectionLength);
            }
            catch (IOException)
            {
            }

            var directories = new List <Directory> {
                directory
            };

            if (photoshopDirectories != null)
            {
                directories.AddRange(photoshopDirectories);
            }

            // LAYER AND MASK INFORMATION SECTION (skipped)

            // IMAGE DATA SECTION (skipped)

            return(directories);
        }
    }
Exemple #12
0
        // PROCESS CHUNKS
        public void ProcessChunks([NotNull] SequentialReader reader, int sizeLeft, [NotNull] IRiffHandler handler)
        {
            // Processing chunks. Each chunk is 8 bytes header (4 bytes CC code + 4 bytes length of chunk) + data of the chunk

            while (reader.Position < sizeLeft)
            {
                // Check if end of the file is closer then 8 bytes
                if (reader.IsCloserToEnd(8))
                {
                    return;
                }

                string chunkFourCc = reader.GetString(4, Encoding.ASCII);
                int    chunkSize   = reader.GetInt32();

                sizeLeft -= 8;

                // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this
                if (chunkSize < 0 || sizeLeft < chunkSize)
                {
                    throw new RiffProcessingException("Invalid RIFF chunk size");
                }

                // Check if end of the file is closer then chunkSize bytes
                if (reader.IsCloserToEnd(chunkSize))
                {
                    return;
                }

                if (chunkFourCc == "LIST" || chunkFourCc == "RIFF")
                {
                    string listName = reader.GetString(4, Encoding.ASCII);
                    if (handler.ShouldAcceptList(listName))
                    {
                        ProcessChunks(reader, sizeLeft - 4, handler);
                    }
                    else
                    {
                        reader.Skip(sizeLeft - 4);
                    }
                    sizeLeft -= chunkSize;
                }
                else
                {
                    if (handler.ShouldAcceptChunk(chunkFourCc))
                    {
                        // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler?
                        handler.ProcessChunk(chunkFourCc, reader.GetBytes(chunkSize));
                    }
                    else
                    {
                        reader.Skip(chunkSize);
                    }

                    sizeLeft -= chunkSize;

                    // Skip any padding byte added to keep chunks aligned to even numbers of bytes
                    if (chunkSize % 2 == 1)
                    {
                        reader.GetSByte();
                        sizeLeft--;
                    }
                }
            }
        }
        // http://id3.org/mp3Frame
        // https://www.loc.gov/preservation/digital/formats/fdd/fdd000105.shtml

        public Directory Extract(SequentialReader reader)
        {
            var directory = new Mp3Directory();

            var header = reader.GetInt32();

            // ID: MPEG-2.5, MPEG-2, or MPEG-1
            int id = 0;

            switch ((header & 0x000180000) >> 19)
            {
            case 0:
                throw new ImageProcessingException("MPEG-2.5 not supported.");

            case 2:
                directory.Set(Mp3Directory.TAG_ID, "MPEG-2");
                id = 2;
                break;

            case 3:
                directory.Set(Mp3Directory.TAG_ID, "MPEG-1");
                id = 1;
                break;
            }

            // Layer Type: 1, 2, 3, or not defined
            int layer = (header & 0x00060000) >> 17;

            switch (layer)
            {
            case 0:
                directory.Set(Mp3Directory.TAG_LAYER, "Not defined");
                break;

            case 1:
                directory.Set(Mp3Directory.TAG_LAYER, "Layer III");
                break;

            case 2:
                directory.Set(Mp3Directory.TAG_LAYER, "Layer II");
                break;

            case 3:
                directory.Set(Mp3Directory.TAG_LAYER, "Layer I");
                break;
            }


            int protectionBit = (header & 0x00010000) >> 16;

            // Bitrate: depends on ID and Layer
            int bitrate = (header & 0x0000F000) >> 12;

            if (bitrate != 0 && bitrate != 15)
            {
                directory.Set(Mp3Directory.TAG_BITRATE, SetBitrate(bitrate, layer, id));
            }

            // Frequency: depends on ID
            int frequency = (header & 0x00000C00) >> 10;

            int[,] frequencyMapping = new int[2, 3]
            {
                { 44100, 48000, 32000 },
                { 22050, 24000, 16000 }
            };
            if (id == 1)
            {
                directory.Set(Mp3Directory.TAG_FREQUENCY, frequencyMapping[0, frequency]);
                frequency = frequencyMapping[0, frequency];
            }
            else if (id == 2)
            {
                directory.Set(Mp3Directory.TAG_FREQUENCY, frequencyMapping[1, frequency]);
                frequency = frequencyMapping[1, frequency];
            }


            int paddingBit = (header & 0x00000200) >> 9;

            // Encoding type: Stereo, Joint Stereo, Dual Channel, or Mono
            int mode = (header & 0x000000C0) >> 6;

            switch (mode)
            {
            case 0:
                directory.Set(Mp3Directory.TAG_MODE, "Stereo");
                break;

            case 1:
                directory.Set(Mp3Directory.TAG_MODE, "Joint stereo");
                break;

            case 2:
                directory.Set(Mp3Directory.TAG_MODE, "Dual channel");
                break;

            case 3:
                directory.Set(Mp3Directory.TAG_MODE, "Mono");
                break;
            }

            // Copyright boolean
            int copyright = (header & 0x00000008) >> 3;

            switch (copyright)
            {
            case 0:
                directory.Set(Mp3Directory.TAG_COPYRIGHT, "False");
                break;

            case 1:
                directory.Set(Mp3Directory.TAG_COPYRIGHT, "True");
                break;
            }

            int emphasis = header & 0x00000003;

            switch (emphasis)
            {
            case 0:
                directory.Set(Mp3Directory.TAG_EMPHASIS, "none");
                break;

            case 1:
                directory.Set(Mp3Directory.TAG_EMPHASIS, "50/15ms");
                break;

            case 3:
                directory.Set(Mp3Directory.TAG_EMPHASIS, "CCITT j.17");
                break;
            }

            int frameSize = SetBitrate(bitrate, layer, id) * 1000 * 144 / frequency;

            directory.Set(Mp3Directory.TAG_FRAME_SIZE, frameSize + " bytes");

            return(directory);
        }
        public virtual void Extract([NotNull] SequentialReader reader, int length, [NotNull] Com.Drew.Metadata.Metadata metadata)
        {
            PhotoshopDirectory directory = new PhotoshopDirectory();

            metadata.AddDirectory(directory);
            // Data contains a sequence of Image Resource Blocks (IRBs):
            //
            // 4 bytes - Signature "8BIM"
            // 2 bytes - Resource identifier
            // String  - Pascal string, padded to make length even
            // 4 bytes - Size of resource data which follows
            // Data    - The resource data, padded to make size even
            //
            // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504
            int pos = 0;

            while (pos < length)
            {
                try
                {
                    // 4 bytes for the signature.  Should always be "8BIM".
                    string signature = reader.GetString(4);
                    if (!signature.Equals("8BIM"))
                    {
                        throw new ImageProcessingException("Expecting 8BIM marker");
                    }
                    pos += 4;
                    // 2 bytes for the resource identifier (tag type).
                    int tagType = reader.GetUInt16();
                    // segment type
                    pos += 2;
                    // A variable number of bytes holding a pascal string (two leading bytes for length).
                    short descriptionLength = reader.GetUInt8();
                    pos += 1;
                    // Some basic bounds checking
                    if (descriptionLength < 0 || descriptionLength + pos > length)
                    {
                        throw new ImageProcessingException("Invalid string length");
                    }
                    // We don't use the string value here
                    reader.Skip(descriptionLength);
                    pos += descriptionLength;
                    // The number of bytes is padded with a trailing zero, if needed, to make the size even.
                    if (pos % 2 != 0)
                    {
                        reader.Skip(1);
                        pos++;
                    }
                    // 4 bytes for the size of the resource data that follows.
                    int byteCount = reader.GetInt32();
                    pos += 4;
                    // The resource data.
                    sbyte[] tagBytes = reader.GetBytes(byteCount);
                    pos += byteCount;
                    // The number of bytes is padded with a trailing zero, if needed, to make the size even.
                    if (pos % 2 != 0)
                    {
                        reader.Skip(1);
                        pos++;
                    }
                    if (tagType == PhotoshopDirectory.TagIptc)
                    {
                        new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), metadata, tagBytes.Length);
                    }
                    else
                    {
                        if (tagType == PhotoshopDirectory.TagIccProfileBytes)
                        {
                            new IccReader().Extract(new ByteArrayReader(tagBytes), metadata);
                        }
                        else
                        {
                            if (tagType == PhotoshopDirectory.TagExifData1 || tagType == PhotoshopDirectory.TagExifData3)
                            {
                                new ExifReader().Extract(new ByteArrayReader(tagBytes), metadata);
                            }
                            else
                            {
                                if (tagType == PhotoshopDirectory.TagXmpData)
                                {
                                    new XmpReader().Extract(tagBytes, metadata);
                                }
                                else
                                {
                                    directory.SetByteArray(tagType, tagBytes);
                                }
                            }
                        }
                    }
                    if (tagType >= unchecked ((int)(0x0fa0)) && tagType <= unchecked ((int)(0x1387)))
                    {
                        PhotoshopDirectory._tagNameMap.Put(tagType, Sharpen.Extensions.StringFormat("Plug-in %d Data", tagType - unchecked ((int)(0x0fa0)) + 1));
                    }
                }
                catch (Exception ex)
                {
                    directory.AddError(ex.Message);
                    return;
                }
            }
        }
        public DirectoryList Extract(SequentialReader reader, int length)
        {
            var directory = new PhotoshopDirectory();

            var directories = new List <Directory> {
                directory
            };

            // Data contains a sequence of Image Resource Blocks (IRBs):
            //
            // 4 bytes - Signature; mostly "8BIM" but "PHUT", "AgHg" and "DCSR" are also found
            // 2 bytes - Resource identifier
            // String  - Pascal string, padded to make length even
            // 4 bytes - Size of resource data which follows
            // Data    - The resource data, padded to make size even
            //
            // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504
            var pos = 0;

            while (pos < length)
            {
                try
                {
                    // 4 bytes for the signature ("8BIM", "PHUT", etc.)
                    var signature = reader.GetString(4, Encoding.UTF8);
                    pos += 4;

                    // 2 bytes for the resource identifier (tag type).
                    var tagType = reader.GetUInt16();
                    pos += 2;

                    // A variable number of bytes holding a pascal string (two leading bytes for length).
                    var descriptionLength = reader.GetByte();
                    pos += 1;

                    // Some basic bounds checking
                    if (descriptionLength + pos > length)
                    {
                        throw new ImageProcessingException("Invalid string length");
                    }

                    // We don't use the string value here
                    reader.Skip(descriptionLength);
                    pos += descriptionLength;

                    // The number of bytes is padded with a trailing zero, if needed, to make the size even.
                    if (pos % 2 != 0)
                    {
                        reader.Skip(1);
                        pos++;
                    }

                    // 4 bytes for the size of the resource data that follows.
                    var byteCount = reader.GetInt32();
                    pos += 4;

                    // The resource data.
                    var tagBytes = reader.GetBytes(byteCount);
                    pos += byteCount;

                    // The number of bytes is padded with a trailing zero, if needed, to make the size even.
                    if (pos % 2 != 0)
                    {
                        reader.Skip(1);
                        pos++;
                    }

                    // Skip any unsupported IRBs
                    if (signature != "8BIM")
                    {
                        continue;
                    }

                    switch (tagType)
                    {
                    case PhotoshopDirectory.TagIptc:
                        var iptcDirectory = new IptcReader().Extract(new SequentialByteArrayReader(tagBytes), tagBytes.Length);
                        iptcDirectory.Parent = directory;
                        directories.Add(iptcDirectory);
                        break;

                    case PhotoshopDirectory.TagIccProfileBytes:
                        var iccDirectory = new IccReader().Extract(new ByteArrayReader(tagBytes));
                        iccDirectory.Parent = directory;
                        directories.Add(iccDirectory);
                        break;

                    case PhotoshopDirectory.TagExifData1:
                    case PhotoshopDirectory.TagExifData3:
                        var exifDirectories = new ExifReader().Extract(new ByteArrayReader(tagBytes));
                        foreach (var exifDirectory in exifDirectories.Where(d => d.Parent == null))
                        {
                            exifDirectory.Parent = directory;
                        }
                        directories.AddRange(exifDirectories);
                        break;

                    case PhotoshopDirectory.TagXmpData:
                        var xmpDirectory = new XmpReader().Extract(tagBytes);
                        xmpDirectory.Parent = directory;
                        directories.Add(xmpDirectory);
                        break;

                    default:
                        directory.Set(tagType, tagBytes);
                        break;
                    }

                    if (tagType >= 0x0fa0 && tagType <= 0x1387)
                    {
                        PhotoshopDirectory.TagNameMap[tagType] = $"Plug-in {tagType - 0x0fa0 + 1} Data";
                    }
                }
                catch (Exception ex)
                {
                    directory.AddError(ex.Message);
                    break;
                }
            }

            return(directories);
        }
        public 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);
        }
Exemple #17
0
        private static void ReadBitmapHeader(SequentialReader reader, BmpHeaderDirectory directory, List <Directory> directories)
        {
            /*
             * BITMAPCOREHEADER (12 bytes):
             *
             *    DWORD Size              - Size of this header in bytes
             *    SHORT Width             - Image width in pixels
             *    SHORT Height            - Image height in pixels
             *    WORD  Planes            - Number of color planes
             *    WORD  BitsPerPixel      - Number of bits per pixel
             *
             * OS21XBITMAPHEADER (12 bytes):
             *
             *    DWORD  Size             - Size of this structure in bytes
             *    WORD   Width            - Bitmap width in pixels
             *    WORD   Height           - Bitmap height in pixel
             *    WORD   NumPlanes        - Number of bit planes (color depth)
             *    WORD   BitsPerPixel     - Number of bits per pixel per plane
             *
             * OS22XBITMAPHEADER (16/64 bytes):
             *
             *    DWORD  Size             - Size of this structure in bytes
             *    DWORD  Width            - Bitmap width in pixels
             *    DWORD  Height           - Bitmap height in pixel
             *    WORD   NumPlanes        - Number of bit planes (color depth)
             *    WORD   BitsPerPixel     - Number of bits per pixel per plane
             *
             *    - Short version ends here -
             *
             *    DWORD  Compression      - Bitmap compression scheme
             *    DWORD  ImageDataSize    - Size of bitmap data in bytes
             *    DWORD  XResolution      - X resolution of display device
             *    DWORD  YResolution      - Y resolution of display device
             *    DWORD  ColorsUsed       - Number of color table indices used
             *    DWORD  ColorsImportant  - Number of important color indices
             *    WORD   Units            - Type of units used to measure resolution
             *    WORD   Reserved         - Pad structure to 4-byte boundary
             *    WORD   Recording        - Recording algorithm
             *    WORD   Rendering        - Halftoning algorithm used
             *    DWORD  Size1            - Reserved for halftoning algorithm use
             *    DWORD  Size2            - Reserved for halftoning algorithm use
             *    DWORD  ColorEncoding    - Color model used in bitmap
             *    DWORD  Identifier       - Reserved for application use
             *
             * BITMAPINFOHEADER (40 bytes), BITMAPV2INFOHEADER (52 bytes), BITMAPV3INFOHEADER (56 bytes),
             * BITMAPV4HEADER (108 bytes) and BITMAPV5HEADER (124 bytes):
             *
             *    DWORD Size              - Size of this header in bytes
             *    LONG  Width             - Image width in pixels
             *    LONG  Height            - Image height in pixels
             *    WORD  Planes            - Number of color planes
             *    WORD  BitsPerPixel      - Number of bits per pixel
             *    DWORD Compression       - Compression methods used
             *    DWORD SizeOfBitmap      - Size of bitmap in bytes
             *    LONG  HorzResolution    - Horizontal resolution in pixels per meter
             *    LONG  VertResolution    - Vertical resolution in pixels per meter
             *    DWORD ColorsUsed        - Number of colors in the image
             *    DWORD ColorsImportant   - Minimum number of important colors
             *
             *    - BITMAPINFOHEADER ends here -
             *
             *    DWORD RedMask           - Mask identifying bits of red component
             *    DWORD GreenMask         - Mask identifying bits of green component
             *    DWORD BlueMask          - Mask identifying bits of blue component
             *
             *    - BITMAPV2INFOHEADER ends here -
             *
             *    DWORD AlphaMask         - Mask identifying bits of alpha component
             *
             *    - BITMAPV3INFOHEADER ends here -
             *
             *    DWORD CSType            - Color space type
             *    LONG  RedX              - X coordinate of red endpoint
             *    LONG  RedY              - Y coordinate of red endpoint
             *    LONG  RedZ              - Z coordinate of red endpoint
             *    LONG  GreenX            - X coordinate of green endpoint
             *    LONG  GreenY            - Y coordinate of green endpoint
             *    LONG  GreenZ            - Z coordinate of green endpoint
             *    LONG  BlueX             - X coordinate of blue endpoint
             *    LONG  BlueY             - Y coordinate of blue endpoint
             *    LONG  BlueZ             - Z coordinate of blue endpoint
             *    DWORD GammaRed          - Gamma red coordinate scale value
             *    DWORD GammaGreen        - Gamma green coordinate scale value
             *    DWORD GammaBlue         - Gamma blue coordinate scale value
             *
             *    - BITMAPV4HEADER ends here -
             *
             *    DWORD Intent            - Rendering intent for bitmap
             *    DWORD ProfileData       - Offset of the profile data relative to BITMAPV5HEADER
             *    DWORD ProfileSize       - Size, in bytes, of embedded profile data
             *    DWORD Reserved          - Shall be zero
             *
             */

            try
            {
                int  bitmapType   = directory.GetInt32(BmpHeaderDirectory.TagBitmapType);
                long headerOffset = reader.Position;
                int  headerSize   = reader.GetInt32();

                directory.Set(BmpHeaderDirectory.TagHeaderSize, headerSize);

                /*
                 * Known header type sizes:
                 *
                 *  12 - BITMAPCOREHEADER or OS21XBITMAPHEADER
                 *  16 - OS22XBITMAPHEADER (short)
                 *  40 - BITMAPINFOHEADER
                 *  52 - BITMAPV2INFOHEADER
                 *  56 - BITMAPV3INFOHEADER
                 *  64 - OS22XBITMAPHEADER (full)
                 * 108 - BITMAPV4HEADER
                 * 124 - BITMAPV5HEADER
                 *
                 */

                if (headerSize == 12 && bitmapType == (int)BmpHeaderDirectory.BitmapType.Bitmap)
                {
                    //BITMAPCOREHEADER

                    /*
                     * There's no way to tell BITMAPCOREHEADER and OS21XBITMAPHEADER
                     * apart for the "standard" bitmap type. The difference is only
                     * that BITMAPCOREHEADER has signed width and height while
                     * in OS21XBITMAPHEADER they are unsigned. Since BITMAPCOREHEADER,
                     * the Windows version, is most common, read them as signed.
                     */
                    directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetInt16());
                    directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetInt16());
                    directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16());
                }
                else if (headerSize == 12)
                {
                    // OS21XBITMAPHEADER
                    directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16());
                }
                else if (headerSize == 16 || headerSize == 64)
                {
                    // OS22XBITMAPHEADER
                    directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16());
                    if (headerSize > 16)
                    {
                        directory.Set(BmpHeaderDirectory.TagCompression, reader.GetInt32());
                        reader.Skip(4); // skip the pixel data length
                        directory.Set(BmpHeaderDirectory.TagXPixelsPerMeter, reader.GetInt32());
                        directory.Set(BmpHeaderDirectory.TagYPixelsPerMeter, reader.GetInt32());
                        directory.Set(BmpHeaderDirectory.TagPaletteColourCount, reader.GetInt32());
                        directory.Set(BmpHeaderDirectory.TagImportantColourCount, reader.GetInt32());
                        reader.Skip(
                            2 + // Skip Units, can only be 0 (pixels per meter)
                            2 + // Skip padding
                            2   // Skip Recording, can only be 0 (left to right, bottom to top)
                            );
                        directory.Set(BmpHeaderDirectory.TagRendering, reader.GetUInt16());
                        reader.Skip(4 + 4); // Skip Size1 and Size2
                        directory.Set(BmpHeaderDirectory.TagColorEncoding, reader.GetInt32());
                        reader.Skip(4);     // Skip Identifier
                    }
                }
                else if (
                    headerSize == 40 || headerSize == 52 || headerSize == 56 ||
                    headerSize == 108 || headerSize == 124)
                {
                    // BITMAPINFOHEADER V1-5
                    directory.Set(BmpHeaderDirectory.TagImageWidth, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagImageHeight, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagColourPlanes, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagBitsPerPixel, reader.GetUInt16());
                    directory.Set(BmpHeaderDirectory.TagCompression, reader.GetInt32());
                    // skip the pixel data length
                    reader.Skip(4);
                    directory.Set(BmpHeaderDirectory.TagXPixelsPerMeter, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagYPixelsPerMeter, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagPaletteColourCount, reader.GetInt32());
                    directory.Set(BmpHeaderDirectory.TagImportantColourCount, reader.GetInt32());
                    if (headerSize == 40)
                    {
                        // BITMAPINFOHEADER end
                        return;
                    }
                    directory.Set(BmpHeaderDirectory.TagRedMask, reader.GetUInt32());
                    directory.Set(BmpHeaderDirectory.TagGreenMask, reader.GetUInt32());
                    directory.Set(BmpHeaderDirectory.TagBlueMask, reader.GetUInt32());
                    if (headerSize == 52)
                    {
                        // BITMAPV2INFOHEADER end
                        return;
                    }
                    directory.Set(BmpHeaderDirectory.TagAlphaMask, reader.GetUInt32());
                    if (headerSize == 56)
                    {
                        // BITMAPV3INFOHEADER end
                        return;
                    }
                    long csType = reader.GetUInt32();
                    directory.Set(BmpHeaderDirectory.TagColorSpaceType, csType);
                    reader.Skip(36); // Skip color endpoint coordinates
                    directory.Set(BmpHeaderDirectory.TagGammaRed, reader.GetUInt32());
                    directory.Set(BmpHeaderDirectory.TagGammaGreen, reader.GetUInt32());
                    directory.Set(BmpHeaderDirectory.TagGammaBlue, reader.GetUInt32());
                    if (headerSize == 108)
                    {
                        // BITMAPV4HEADER end
                        return;
                    }
                    directory.Set(BmpHeaderDirectory.TagIntent, reader.GetInt32());
                    if (csType == (long)BmpHeaderDirectory.ColorSpaceType.ProfileEmbedded || csType == (long)BmpHeaderDirectory.ColorSpaceType.ProfileLinked)
                    {
                        long profileOffset = reader.GetUInt32();
                        int  profileSize   = reader.GetInt32();
                        if (reader.Position > headerOffset + profileOffset)
                        {
                            directory.AddError("Invalid profile data offset 0x" + (headerOffset + profileOffset).ToString("X8"));
                            return;
                        }
                        reader.Skip(headerOffset + profileOffset - reader.Position);
                        if (csType == (long)BmpHeaderDirectory.ColorSpaceType.ProfileLinked)
                        {
                            directory.Set(BmpHeaderDirectory.TagLinkedProfile, reader.GetNullTerminatedString(profileSize, Encoding.GetEncoding(1252)));
                        }
                        else
                        {
                            var iccReader    = new ByteArrayReader(reader.GetBytes(profileSize));
                            var iccDirectory = new IccReader().Extract(iccReader);
                            iccDirectory.Parent = directory;
                            directories.Add(iccDirectory);
                        }
                    }
                    else
                    {
                        reader.Skip(
                            4 + // Skip ProfileData offset
                            4 + // Skip ProfileSize
                            4   // Skip Reserved
                            );
                    }
                }
                else
                {
                    directory.AddError("Unexpected DIB header size: " + headerSize);
                }
            }
            catch (IOException)
            {
                directory.AddError("Unable to read BMP header");
            }
            catch (MetadataException)
            {
                directory.AddError("Internal error");
            }
        }