Exemple #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);
        }
        public DuckyDirectory Extract(SequentialReader reader)
        {
            var directory = new DuckyDirectory();

            try
            {
                while (true)
                {
                    var tag = reader.GetUInt16();

                    // End of Segment is marked with zero
                    if (tag == 0)
                    {
                        break;
                    }

                    var length = reader.GetUInt16();

                    switch (tag)
                    {
                    case DuckyDirectory.TagQuality:
                    {
                        if (length != 4)
                        {
                            directory.AddError("Unexpected length for the quality tag");
                            return(directory);
                        }
                        directory.Set(tag, reader.GetUInt32());
                        break;
                    }

                    case DuckyDirectory.TagComment:
                    case DuckyDirectory.TagCopyright:
                    {
                        reader.Skip(4);
                        directory.Set(tag, reader.GetString(length - 4, Encoding.BigEndianUnicode));
                        break;
                    }

                    default:
                    {
                        // Unexpected tag
                        directory.Set(tag, reader.GetBytes(length));
                        break;
                    }
                    }
                }
            }
            catch (IOException e)
            {
                directory.AddError(e.Message);
            }

            return(directory);
        }
Exemple #3
0
        public virtual void Extract([NotNull] SequentialReader reader, [NotNull] Com.Drew.Metadata.Metadata metadata)
        {
            reader.SetMotorolaByteOrder(false);
            PcxDirectory directory = new PcxDirectory();

            metadata.AddDirectory(directory);
            try
            {
                sbyte identifier = reader.GetInt8();
                if (identifier != unchecked ((int)(0x0A)))
                {
                    throw new ImageProcessingException("Invalid PCX identifier byte");
                }
                directory.SetInt(PcxDirectory.TagVersion, reader.GetInt8());
                sbyte encoding = reader.GetInt8();
                if (encoding != unchecked ((int)(0x01)))
                {
                    throw new ImageProcessingException("Invalid PCX encoding byte");
                }
                directory.SetInt(PcxDirectory.TagBitsPerPixel, reader.GetUInt8());
                directory.SetInt(PcxDirectory.TagXmin, reader.GetUInt16());
                directory.SetInt(PcxDirectory.TagYmin, reader.GetUInt16());
                directory.SetInt(PcxDirectory.TagXmax, reader.GetUInt16());
                directory.SetInt(PcxDirectory.TagYmax, reader.GetUInt16());
                directory.SetInt(PcxDirectory.TagHorizontalDpi, reader.GetUInt16());
                directory.SetInt(PcxDirectory.TagVerticalDpi, reader.GetUInt16());
                directory.SetByteArray(PcxDirectory.TagPalette, reader.GetBytes(48));
                reader.Skip(1);
                directory.SetInt(PcxDirectory.TagColorPlanes, reader.GetUInt8());
                directory.SetInt(PcxDirectory.TagBytesPerLine, reader.GetUInt16());
                int paletteType = reader.GetUInt16();
                if (paletteType != 0)
                {
                    directory.SetInt(PcxDirectory.TagPaletteType, paletteType);
                }
                int hScrSize = reader.GetUInt16();
                if (hScrSize != 0)
                {
                    directory.SetInt(PcxDirectory.TagHscrSize, hScrSize);
                }
                int vScrSize = reader.GetUInt16();
                if (vScrSize != 0)
                {
                    directory.SetInt(PcxDirectory.TagVscrSize, vScrSize);
                }
            }
            catch (Exception ex)
            {
                directory.AddError("Exception reading PCX file metadata: " + ex.Message);
            }
        }
Exemple #4
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--;
                }
            }
        }
        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();
        }
Exemple #7
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);
                    }
                }
            }
        }
Exemple #8
0
        private static void PopulateEx(WavFormatDirectory directory, SequentialReader reader, int exSize)
        {
            if (exSize < 2)
            {
                return;
            }
            directory.Set(TagValidBitsPerSample, reader.GetUInt16());

            if (exSize < 6)
            {
                return;
            }
            directory.Set(TagChannelMask, reader.GetUInt32());

            if (exSize < 22)
            {
                return;
            }
            directory.Set(TagSubformat, reader.GetBytes(16));
        }
        public virtual void Extract([NotNull] SequentialReader reader, int length, [NotNull] Com.Drew.Metadata.Metadata metadata)
        {
            PhotoshopDirectory directory = new PhotoshopDirectory();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return(directories);
        }
Exemple #11
0
        public static JpegSegmentData ReadSegments([NotNull] SequentialReader reader, [CanBeNull] ICollection <JpegSegmentType> segmentTypes = null)
        {
            // Must be big-endian
            Debug.Assert(reader.IsMotorolaByteOrder);

            // first two bytes should be JPEG magic number
            var magicNumber = reader.GetUInt16();

            if (magicNumber != 0xFFD8)
            {
                throw new JpegProcessingException($"JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x{magicNumber:X4}");
            }

            var segmentData = new JpegSegmentData();

            do
            {
                // Find the segment marker. Markers are zero or more 0xFF bytes, followed
                // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
                var segmentIdentifier = reader.GetByte();

                // We must have at least one 0xFF byte
                if (segmentIdentifier != 0xFF)
                {
                    throw new JpegProcessingException($"Expected JPEG segment start identifier 0xFF, not 0x{segmentIdentifier:X2}");
                }

                // Read until we have a non-0xFF byte. This identifies the segment type.
                var segmentTypeByte = reader.GetByte();
                while (segmentTypeByte == 0xFF)
                {
                    segmentTypeByte = reader.GetByte();
                }

                if (segmentTypeByte == 0)
                {
                    throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier");
                }

                var segmentType = (JpegSegmentType)segmentTypeByte;

                if (segmentType == JpegSegmentType.Sos)
                {
                    // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
                    // have to search for the two bytes: 0xFF 0xD9 (EOI).
                    // It comes last so simply return at this point
                    return(segmentData);
                }

                if (segmentType == JpegSegmentType.Eoi)
                {
                    // the 'End-Of-Image' segment -- this should never be found in this fashion
                    return(segmentData);
                }

                // next 2-bytes are <segment-size>: [high-byte] [low-byte]
                var segmentLength = (int)reader.GetUInt16();

                // segment length includes size bytes, so subtract two
                segmentLength -= 2;

                if (segmentLength < 0)
                {
                    throw new JpegProcessingException("JPEG segment size would be less than zero");
                }

                // Check whether we are interested in this segment
                if (segmentTypes == null || segmentTypes.Contains(segmentType))
                {
                    var segmentBytes = reader.GetBytes(segmentLength);
                    Debug.Assert(segmentLength == segmentBytes.Length);
                    segmentData.AddSegment(segmentType, segmentBytes);
                }
                else
                {
                    // Some of the JPEG is truncated, so just return what data we've already gathered
                    if (!reader.TrySkip(segmentLength))
                    {
                        return(segmentData);
                    }
                }
            }while (true);
        }
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--;
                    }
                }
            }
        }
        public IEnumerable <PngChunk> Extract([NotNull] SequentialReader reader, [CanBeNull] ICollection <PngChunkType> desiredChunkTypes)
        {
            //
            // PNG DATA STREAM
            //
            // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS.
            //
            // PNG SIGNATURE
            //
            //   Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A
            //
            // CHUNK
            //
            //   4 - length of the data field (unsigned, but always within 31 bytes), may be zero
            //   4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z)
            //   * - data field
            //   4 - CRC calculated from chunk type and chunk data, but not length
            //
            // CHUNK TYPES
            //
            //   Critical Chunk Types:
            //
            //     IHDR - image header, always the first chunk in the data stream
            //     PLTE - palette table, associated with indexed PNG images
            //     IDAT - image data chunk, of which there may be many
            //     IEND - image trailer, always the last chunk in the data stream
            //
            //   Ancillary Chunk Types:
            //
            //     Transparency information:  tRNS
            //     Colour space information:  cHRM, gAMA, iCCP, sBIT, sRGB
            //     Textual information:       iTXt, tEXt, zTXt
            //     Miscellaneous information: bKGD, hIST, pHYs, sPLT
            //     Time information:          tIME
            //

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

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

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

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

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

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

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

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

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

                seenChunkTypes.Add(chunkType);
            }

            return(chunks);
        }
        /// <exception cref="System.IO.IOException"/>
        private void ProcessTag([NotNull] SequentialReader reader, [NotNull] Com.Drew.Metadata.Directory directory, int directoryType, int tagType, int tagByteCount)
        {
            int tagIdentifier = tagType | (directoryType << 8);

            // Some images have been seen that specify a zero byte tag, which cannot be of much use.
            // We elect here to completely ignore the tag. The IPTC specification doesn't mention
            // anything about the interpretation of this situation.
            // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf
            if (tagByteCount == 0)
            {
                directory.SetString(tagIdentifier, string.Empty);
                return;
            }
            string @string = null;

            switch (tagIdentifier)
            {
            case IptcDirectory.TagCodedCharacterSet:
            {
                sbyte[] bytes   = reader.GetBytes(tagByteCount);
                string  charset = Iso2022Converter.ConvertISO2022CharsetToJavaCharset(bytes);
                if (charset == null)
                {
                    // Unable to determine the charset, so fall through and treat tag as a regular string
                    @string = Sharpen.Runtime.GetStringForBytes(bytes);
                    break;
                }
                directory.SetString(tagIdentifier, charset);
                return;
            }

            case IptcDirectory.TagEnvelopeRecordVersion:
            case IptcDirectory.TagApplicationRecordVersion:
            case IptcDirectory.TagFileVersion:
            case IptcDirectory.TagArmVersion:
            case IptcDirectory.TagProgramVersion:
            {
                // short
                if (tagByteCount >= 2)
                {
                    int shortValue = reader.GetUInt16();
                    reader.Skip(tagByteCount - 2);
                    directory.SetInt(tagIdentifier, shortValue);
                    return;
                }
                break;
            }

            case IptcDirectory.TagUrgency:
            {
                // byte
                directory.SetInt(tagIdentifier, reader.GetUInt8());
                reader.Skip(tagByteCount - 1);
                return;
            }

            case IptcDirectory.TagReleaseDate:
            case IptcDirectory.TagDateCreated:
            {
                // Date object
                if (tagByteCount >= 8)
                {
                    @string = reader.GetString(tagByteCount);
                    try
                    {
                        int      year  = System.Convert.ToInt32(Sharpen.Runtime.Substring(@string, 0, 4));
                        int      month = System.Convert.ToInt32(Sharpen.Runtime.Substring(@string, 4, 6)) - 1;
                        int      day   = System.Convert.ToInt32(Sharpen.Runtime.Substring(@string, 6, 8));
                        DateTime date  = new Sharpen.GregorianCalendar(year, month, day).GetTime();
                        directory.SetDate(tagIdentifier, date);
                        return;
                    }
                    catch (FormatException)
                    {
                    }
                }
                else
                {
                    // fall through and we'll process the 'string' value below
                    reader.Skip(tagByteCount);
                }
                goto case IptcDirectory.TagReleaseTime;
            }

            case IptcDirectory.TagReleaseTime:
            case IptcDirectory.TagTimeCreated:
            default:
            {
                break;
            }
            }
            // time...
            // fall through
            // If we haven't returned yet, treat it as a string
            // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value
            if (@string == null)
            {
                string encoding = directory.GetString(IptcDirectory.TagCodedCharacterSet);
                if (encoding != null)
                {
                    @string = reader.GetString(tagByteCount, encoding);
                }
                else
                {
                    sbyte[] bytes_1 = reader.GetBytes(tagByteCount);
                    encoding = Iso2022Converter.GuessEncoding(bytes_1);
                    @string  = encoding != null?Sharpen.Runtime.GetStringForBytes(bytes_1, encoding) : Sharpen.Runtime.GetStringForBytes(bytes_1);
                }
            }
            if (directory.ContainsTag(tagIdentifier))
            {
                // this fancy string[] business avoids using an ArrayList for performance reasons
                string[] oldStrings = directory.GetStringArray(tagIdentifier);
                string[] newStrings;
                if (oldStrings == null)
                {
                    newStrings = new string[1];
                }
                else
                {
                    newStrings = new string[oldStrings.Length + 1];
                    System.Array.Copy(oldStrings, 0, newStrings, 0, oldStrings.Length);
                }
                newStrings[newStrings.Length - 1] = @string;
                directory.SetStringArray(tagIdentifier, newStrings);
            }
            else
            {
                directory.SetString(tagIdentifier, @string);
            }
        }
        public static JpegSegmentData ReadSegments([NotNull] SequentialReader reader, [CanBeNull] Iterable <JpegSegmentType> segmentTypes)
        {
            // Must be big-endian
            System.Diagnostics.Debug.Assert((reader.IsMotorolaByteOrder()));
            // first two bytes should be JPEG magic number
            int magicNumber = reader.GetUInt16();

            if (magicNumber != unchecked ((int)(0xFFD8)))
            {
                throw new JpegProcessingException("JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x" + Sharpen.Extensions.ToHexString(magicNumber));
            }
            ICollection <sbyte> segmentTypeBytes = null;

            if (segmentTypes != null)
            {
                segmentTypeBytes = new HashSet <sbyte>();
                foreach (JpegSegmentType segmentType in segmentTypes)
                {
                    segmentTypeBytes.Add(segmentType.byteValue);
                }
            }
            JpegSegmentData segmentData = new JpegSegmentData();

            do
            {
                // Find the segment marker. Markers are zero or more 0xFF bytes, followed
                // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
                short segmentIdentifier = reader.GetUInt8();
                // We must have at least one 0xFF byte
                if (segmentIdentifier != unchecked ((int)(0xFF)))
                {
                    throw new JpegProcessingException("Expected JPEG segment start identifier 0xFF, not 0x" + Sharpen.Extensions.ToHexString(segmentIdentifier).ToUpper());
                }
                // Read until we have a non-0xFF byte. This identifies the segment type.
                sbyte segmentType = reader.GetInt8();
                while (segmentType == unchecked ((sbyte)0xFF))
                {
                    segmentType = reader.GetInt8();
                }
                if (segmentType == 0)
                {
                    throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier");
                }
                if (segmentType == SegmentSos)
                {
                    // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
                    // have to search for the two bytes: 0xFF 0xD9 (EOI).
                    // It comes last so simply return at this point
                    return(segmentData);
                }
                if (segmentType == MarkerEoi)
                {
                    // the 'End-Of-Image' segment -- this should never be found in this fashion
                    return(segmentData);
                }
                // next 2-bytes are <segment-size>: [high-byte] [low-byte]
                int segmentLength = reader.GetUInt16();
                // segment length includes size bytes, so subtract two
                segmentLength -= 2;
                if (segmentLength < 0)
                {
                    throw new JpegProcessingException("JPEG segment size would be less than zero");
                }
                // Check whether we are interested in this segment
                if (segmentTypeBytes == null || segmentTypeBytes.Contains(segmentType))
                {
                    sbyte[] segmentBytes = reader.GetBytes(segmentLength);
                    System.Diagnostics.Debug.Assert((segmentLength == segmentBytes.Length));
                    segmentData.AddSegment(segmentType, segmentBytes);
                }
                else
                {
                    // Some if the JPEG is truncated, just return what data we've already gathered
                    if (!reader.TrySkip(segmentLength))
                    {
                        return(segmentData);
                    }
                }
            }while (true);
        }
Exemple #16
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);
        }
        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);
        }
Exemple #18
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());
        }
Exemple #19
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");
            }
        }
        private static void ProcessTag([NotNull] SequentialReader reader, [NotNull] Directory directory, int directoryType, int tagType, int tagByteCount)
        {
            var tagIdentifier = tagType | (directoryType << 8);

            // Some images have been seen that specify a zero byte tag, which cannot be of much use.
            // We elect here to completely ignore the tag. The IPTC specification doesn't mention
            // anything about the interpretation of this situation.
            // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf
            if (tagByteCount == 0)
            {
                directory.Set(tagIdentifier, string.Empty);
                return;
            }

            string str = null;

            switch (tagIdentifier)
            {
            case IptcDirectory.TagCodedCharacterSet:
            {
                var bytes   = reader.GetBytes(tagByteCount);
                var charset = Iso2022Converter.ConvertEscapeSequenceToEncodingName(bytes);
                if (charset == null)
                {
                    // Unable to determine the charset, so fall through and treat tag as a regular string
                    str = Encoding.UTF8.GetString(bytes);
                    break;
                }
                directory.Set(tagIdentifier, charset);
                return;
            }

            case IptcDirectory.TagEnvelopeRecordVersion:
            case IptcDirectory.TagApplicationRecordVersion:
            case IptcDirectory.TagFileVersion:
            case IptcDirectory.TagArmVersion:
            case IptcDirectory.TagProgramVersion:
            {
                // short
                if (tagByteCount >= 2)
                {
                    var shortValue = reader.GetUInt16();
                    reader.Skip(tagByteCount - 2);
                    directory.Set(tagIdentifier, shortValue);
                    return;
                }
                break;
            }

            case IptcDirectory.TagUrgency:
            {
                // byte
                directory.Set(tagIdentifier, reader.GetByte());
                reader.Skip(tagByteCount - 1);
                return;
            }

            case IptcDirectory.TagReleaseDate:
            case IptcDirectory.TagDateCreated:
            case IptcDirectory.TagDigitalDateCreated:
            {
                // Date object
                if (tagByteCount >= 8)
                {
                    str = reader.GetString(tagByteCount);
                    Debug.Assert(str.Length >= 8);

                    int year, month, day;
                    if (int.TryParse(str.Substring(0, 4), out year) &&
                        int.TryParse(str.Substring(4, 2), out month) &&
                        int.TryParse(str.Substring(6, 2), out day) &&
                        DateUtil.IsValidDate(year, month, day))
                    {
                        directory.Set(tagIdentifier, new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Unspecified));
                        return;
                    }
                }
                else
                {
                    // fall through and we'll process the 'string' value below
                    reader.Skip(tagByteCount);
                }
                break;
            }
            }

            // If we haven't returned yet, treat it as a string
            // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value
            if (str == null)
            {
                var      encodingName = directory.GetString(IptcDirectory.TagCodedCharacterSet);
                Encoding encoding     = null;
                if (encodingName != null)
                {
                    try
                    {
                        encoding = Encoding.GetEncoding(encodingName);
                    }
                    catch { }
                }

                var bytes = reader.GetBytes(tagByteCount);

                if (encoding == null)
                {
                    encoding = Iso2022Converter.GuessEncoding(bytes);
                }

                if (encoding == null)
                {
                    encoding = Encoding.UTF8;
                }

                str = encoding.GetString(bytes);
            }

            if (directory.ContainsTag(tagIdentifier))
            {
                // this fancy string[] business avoids using an ArrayList for performance reasons
                var oldStrings = directory.GetStringArray(tagIdentifier);

                string[] newStrings;
                if (oldStrings == null)
                {
                    // TODO hitting this block means any prior value(s) are discarded
                    newStrings = new string[1];
                }
                else
                {
                    newStrings = new string[oldStrings.Length + 1];
                    Array.Copy(oldStrings, 0, newStrings, 0, oldStrings.Length);
                }
                newStrings[newStrings.Length - 1] = str;
                directory.Set(tagIdentifier, newStrings);
            }
            else
            {
                directory.Set(tagIdentifier, str);
            }
        }
        public DirectoryList Extract(SequentialReader reader, int length)
        {
            var directory = new PhotoshopDirectory();

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

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

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

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

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

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

                    // Get name (important for paths)
                    var description = new StringBuilder();
                    // Loop through each byte and append to string
                    while (descriptionLength > 0)
                    {
                        description.Append((char)reader.GetByte());
                        pos++;
                        descriptionLength--;
                    }

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

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

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

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

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

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

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

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

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

                    default:
                        if (tagType >= PhotoshopDirectory.TagClippingPathBlockStart && tagType <= PhotoshopDirectory.TagClippingPathBlockEnd)
                        {
                            clippingPathCount++;
                            Array.Resize(ref tagBytes, tagBytes.Length + description.Length + 1);
                            // Append description(name) to end of byte array with 1 byte before the description representing the length
                            for (int i = tagBytes.Length - description.Length - 1; i < tagBytes.Length; i++)
                            {
                                if (i % (tagBytes.Length - description.Length - 1 + description.Length) == 0)
                                {
                                    tagBytes[i] = (byte)description.Length;
                                }
                                else
                                {
                                    tagBytes[i] = (byte)description[i - (tagBytes.Length - description.Length - 1)];
                                }
                            }
                            PhotoshopDirectory.TagNameMap[PhotoshopDirectory.TagClippingPathBlockStart + clippingPathCount - 1] = "Path Info " + clippingPathCount;
                            directory.Set(PhotoshopDirectory.TagClippingPathBlockStart + clippingPathCount - 1, tagBytes);
                        }
                        else
                        {
                            directory.Set(tagType, tagBytes);
                        }
                        break;
                    }

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

            return(directories);
        }
Exemple #22
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--;
                }
            }
        }
        private static void ProcessTag(SequentialReader reader, Directory directory, int directoryType, int tagType, int tagByteCount)
        {
            var tagIdentifier = tagType | (directoryType << 8);

            // Some images have been seen that specify a zero byte tag, which cannot be of much use.
            // We elect here to completely ignore the tag. The IPTC specification doesn't mention
            // anything about the interpretation of this situation.
            // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf
            if (tagByteCount == 0)
            {
                directory.Set(tagIdentifier, string.Empty);
                return;
            }

            switch (tagIdentifier)
            {
            case IptcDirectory.TagCodedCharacterSet:
            {
                var bytes   = reader.GetBytes(tagByteCount);
                var charset = Iso2022Converter.ConvertEscapeSequenceToEncodingName(bytes);
                if (charset == null)
                {
                    // Unable to determine the charset, so fall through and treat tag as a regular string
                    charset = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
                }
                directory.Set(tagIdentifier, charset);
                return;
            }

            case IptcDirectory.TagEnvelopeRecordVersion:
            case IptcDirectory.TagApplicationRecordVersion:
            case IptcDirectory.TagFileVersion:
            case IptcDirectory.TagArmVersion:
            case IptcDirectory.TagProgramVersion:
            {
                // short
                if (tagByteCount == 2)
                {
                    var shortValue = reader.GetUInt16();
                    reader.Skip(tagByteCount - 2);
                    directory.Set(tagIdentifier, shortValue);
                    return;
                }
                break;
            }

            case IptcDirectory.TagUrgency:
            {
                // byte
                directory.Set(tagIdentifier, reader.GetByte());
                reader.Skip(tagByteCount - 1);
                return;
            }
            }

            // If we haven't returned yet, treat it as a string
            // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value
            var      encodingName = directory.GetString(IptcDirectory.TagCodedCharacterSet);
            Encoding?encoding     = null;

            if (encodingName != null)
            {
                try
                {
                    encoding = Encoding.GetEncoding(encodingName);
                }
                catch (ArgumentException)
                { }
            }

            StringValue str;

            if (encoding != null)
            {
                str = reader.GetStringValue(tagByteCount, encoding);
            }
            else
            {
                var bytes = reader.GetBytes(tagByteCount);
                encoding = Iso2022Converter.GuessEncoding(bytes);
                str      = new StringValue(bytes, encoding);
            }

            if (directory.ContainsTag(tagIdentifier))
            {
                // this fancy string[] business avoids using an ArrayList for performance reasons
                var oldStrings = directory.GetStringValueArray(tagIdentifier);

                StringValue[] newStrings;
                if (oldStrings == null)
                {
                    // TODO hitting this block means any prior value(s) are discarded
                    newStrings = new StringValue[1];
                }
                else
                {
                    newStrings = new StringValue[oldStrings.Length + 1];
                    Array.Copy(oldStrings, 0, newStrings, 0, oldStrings.Length);
                }
                newStrings[newStrings.Length - 1] = str;
                directory.Set(tagIdentifier, newStrings);
            }
            else
            {
                directory.Set(tagIdentifier, str);
            }
        }
 public PixelInformationBox(BoxLocation location, SequentialReader reader)
     : base(location, reader)
 {
     ChannelCount   = reader.GetByte();
     BitsPerChannel = reader.GetBytes(ChannelCount);
 }