Cold path optimizations for throwing tiff format based exceptions.
Example #1
0
        /// <inheritdoc/>
        public Image <TPixel> Decode <TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            this.inputStream = stream;
            var reader = new DirectoryReader(stream);

            IEnumerable <ExifProfile> directories = reader.Read();

            this.byteOrder = reader.ByteOrder;

            var frames = new List <ImageFrame <TPixel> >();

            foreach (ExifProfile ifd in directories)
            {
                cancellationToken.ThrowIfCancellationRequested();
                ImageFrame <TPixel> frame = this.DecodeFrame <TPixel>(ifd, cancellationToken);
                frames.Add(frame);
            }

            ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder);

            // TODO: Tiff frames can have different sizes
            ImageFrame <TPixel> root = frames[0];

            this.Dimensions = root.Size();
            foreach (ImageFrame <TPixel> frame in frames)
            {
                if (frame.Size() != root.Size())
                {
                    TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported");
                }
            }

            return(new Image <TPixel>(this.Configuration, metadata, frames));
        }
Example #2
0
        /// <summary>
        /// Gets the height of the image frame.
        /// </summary>
        /// <param name="exifProfile">The image frame exif profile.</param>
        /// <returns>The image height.</returns>
        private static int GetImageHeight(ExifProfile exifProfile)
        {
            IExifValue <Number> height = exifProfile.GetValue(ExifTag.ImageLength);

            if (height == null)
            {
                TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength");
            }

            return((int)height.Value);
        }
Example #3
0
        /// <summary>
        /// Gets the width of the image frame.
        /// </summary>
        /// <param name="exifProfile">The image frame exif profile.</param>
        /// <returns>The image width.</returns>
        private static int GetImageWidth(ExifProfile exifProfile)
        {
            IExifValue <Number> width = exifProfile.GetValue(ExifTag.ImageWidth);

            if (width == null)
            {
                TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth");
            }

            return((int)width.Value);
        }
Example #4
0
        /// <summary>
        /// Gets the width of the image frame.
        /// </summary>
        /// <param name="exifProfile">The image frame exif profile.</param>
        /// <returns>The image width.</returns>
        private static int GetImageWidth(ExifProfile exifProfile)
        {
            IExifValue <Number> width = exifProfile.GetValue(ExifTag.ImageWidth);

            if (width == null)
            {
                TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth");
            }

            DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth));

            return((int)width.Value);
        }
        public static ImageMetadata Create <TPixel>(List <ImageFrame <TPixel> > frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            if (frames.Count < 1)
            {
                TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
            }

            ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].Metadata.ExifProfile);

            if (!ignoreMetadata)
            {
                for (int i = 0; i < frames.Count; i++)
                {
                    ImageFrame <TPixel> frame         = frames[i];
                    ImageFrameMetadata  frameMetaData = frame.Metadata;
                    if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes))
Example #6
0
        private static ByteOrder ReadByteOrder(Stream stream)
        {
            Span <byte> headerBytes = stackalloc byte[2];

            stream.Read(headerBytes);
            if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
            {
                return(ByteOrder.LittleEndian);
            }

            if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
            {
                return(ByteOrder.BigEndian);
            }

            throw TiffThrowHelper.ThrowInvalidHeader();
        }
        private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata)
        {
            if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null)
            {
                TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!");
            }

            if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null)
            {
                TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!");
            }

            if (frameMetadata.BitsPerPixel == null)
            {
                TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!");
            }
        }
Example #8
0
        private IEnumerable <ExifProfile> ReadIfds(bool isBigTiff)
        {
            var readers = new List <EntryReader>();

            while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length)
            {
                var reader = new EntryReader(this.stream, this.ByteOrder, this.allocator);
                reader.ReadTags(isBigTiff, this.nextIfdOffset);

                if (reader.BigValues.Count > 0)
                {
                    reader.BigValues.Sort((t1, t2) => t1.Offset.CompareTo(t2.Offset));

                    // this means that most likely all elements are placed  before next IFD
                    if (reader.BigValues[0].Offset < reader.NextIfdOffset)
                    {
                        reader.ReadBigValues();
                    }
                }

                this.nextIfdOffset = reader.NextIfdOffset;
                readers.Add(reader);

                if (readers.Count >= DirectoryMax)
                {
                    TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories");
                }
            }

            var list = new List <ExifProfile>(readers.Count);

            foreach (EntryReader reader in readers)
            {
                reader.ReadBigValues();
                var profile = new ExifProfile(reader.Values, reader.InvalidTags);
                list.Add(profile);
            }

            return(list);
        }
        public static ImageMetadata Create<TPixel>(List<ImageFrame<TPixel>> frames, bool ignoreMetadata, ByteOrder byteOrder)
            where TPixel : unmanaged, IPixel<TPixel>
        {
            if (frames.Count < 1)
            {
                TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
            }

            var imageMetaData = new ImageMetadata();
            ExifProfile exifProfileRootFrame = frames[0].Metadata.ExifProfile;

            SetResolution(imageMetaData, exifProfileRootFrame);

            TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();
            tiffMetadata.ByteOrder = byteOrder;

            if (!ignoreMetadata)
            {
                for (int i = 0; i < frames.Count; i++)
                {
                    ImageFrame<TPixel> frame = frames[i];
                    ImageFrameMetadata frameMetaData = frame.Metadata;
                    if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes))
Example #10
0
        /// <summary>
        /// Calculates the size (in bytes) for a pixel buffer using the determined color format.
        /// </summary>
        /// <param name="width">The width for the desired pixel buffer.</param>
        /// <param name="height">The height for the desired pixel buffer.</param>
        /// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
        /// <returns>The size (in bytes) of the required pixel buffer.</returns>
        private int CalculateStripBufferSize(int width, int height, int plane = -1)
        {
            DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));

            int bitsPerPixel = 0;

            if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
            {
                DebugGuard.IsTrue(plane == -1, "Expected Chunky planar.");
                bitsPerPixel = this.BitsPerPixel;
            }
            else
            {
                switch (plane)
                {
                case 0:
                    bitsPerPixel = this.BitsPerSample.Channel0;
                    break;

                case 1:
                    bitsPerPixel = this.BitsPerSample.Channel1;
                    break;

                case 2:
                    bitsPerPixel = this.BitsPerSample.Channel2;
                    break;

                default:
                    TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported");
                    break;
                }
            }

            int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;

            return(bytesPerRow * height);
        }
        private static void ParseCompression(this TiffDecoderCore options, TiffCompression?compression, ExifProfile exifProfile)
        {
            // Default 1 (No compression) https://www.awaresystems.be/imaging/tiff/tifftags/compression.html
            switch (compression ?? TiffCompression.None)
            {
            case TiffCompression.None:
            {
                options.CompressionType = TiffDecoderCompressionType.None;
                break;
            }

            case TiffCompression.PackBits:
            {
                options.CompressionType = TiffDecoderCompressionType.PackBits;
                break;
            }

            case TiffCompression.Deflate:
            case TiffCompression.OldDeflate:
            {
                options.CompressionType = TiffDecoderCompressionType.Deflate;
                break;
            }

            case TiffCompression.Lzw:
            {
                options.CompressionType = TiffDecoderCompressionType.Lzw;
                break;
            }

            case TiffCompression.CcittGroup3Fax:
            {
                options.CompressionType       = TiffDecoderCompressionType.T4;
                options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None;

                break;
            }

            case TiffCompression.CcittGroup4Fax:
            {
                options.CompressionType       = TiffDecoderCompressionType.T6;
                options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None;

                break;
            }

            case TiffCompression.Ccitt1D:
            {
                options.CompressionType = TiffDecoderCompressionType.HuffmanRle;
                break;
            }

            case TiffCompression.Jpeg:
            {
                options.CompressionType = TiffDecoderCompressionType.Jpeg;
                break;
            }

            default:
            {
                TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported");
                break;
            }
            }
        }
        /// <summary>
        /// Determines the TIFF compression and color types, and reads any associated parameters.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="exifProfile">The exif profile of the frame to decode.</param>
        /// <param name="frameMetadata">The IFD entries container to read the image format information for current frame.</param>
        public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata)
        {
            if (exifProfile.GetValueInternal(ExifTag.TileOffsets) is not null || exifProfile.GetValueInternal(ExifTag.TileByteCounts) is not null)
            {
                TiffThrowHelper.ThrowNotSupported("Tiled images are not supported.");
            }

            if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null)
            {
                TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported.");
            }

            TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst;

            if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1)
            {
                TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's.");
            }

            if (frameMetadata.Predictor == TiffPredictor.FloatingPoint)
            {
                TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported.");
            }

            TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
            TiffSampleFormat?  sampleFormat  = null;

            if (sampleFormats != null)
            {
                sampleFormat = sampleFormats[0];
                foreach (TiffSampleFormat format in sampleFormats)
                {
                    if (format != TiffSampleFormat.UnsignedInteger && format != TiffSampleFormat.Float)
                    {
                        TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat.");
                    }
                }
            }

            ushort[] ycbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
            if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2)
            {
                TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values.");
            }

            if (ycbcrSubSampling != null && ycbcrSubSampling[1] > ycbcrSubSampling[0])
            {
                TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz.");
            }

            if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null)
            {
                TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported.");
            }

            VerifyRequiredFieldsArePresent(exifProfile, frameMetadata);

            options.PlanarConfiguration       = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration;
            options.Predictor                 = frameMetadata.Predictor ?? TiffPredictor.None;
            options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
            options.SampleFormat              = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
            options.BitsPerPixel              = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
            options.BitsPerSample             = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
            options.ReferenceBlackAndWhite    = exifProfile.GetValue(ExifTag.ReferenceBlackWhite)?.Value;
            options.YcbcrCoefficients         = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
            options.YcbcrSubSampling          = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
            options.FillOrder                 = fillOrder;
            options.JpegTables                = exifProfile.GetValue(ExifTag.JPEGTables)?.Value;

            options.ParseColorType(exifProfile);
            options.ParseCompression(frameMetadata.Compression, exifProfile);
        }
        private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile)
        {
            switch (options.PhotometricInterpretation)
            {
            case TiffPhotometricInterpretation.WhiteIsZero:
            {
                if (options.BitsPerSample.Channels != 1)
                {
                    TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
                }

                ushort bitsPerChannel = options.BitsPerSample.Channel0;
                if (bitsPerChannel > 32)
                {
                    TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
                }

                switch (bitsPerChannel)
                {
                case 32:
                {
                    if (options.SampleFormat == TiffSampleFormat.Float)
                    {
                        options.ColorType = TiffColorType.WhiteIsZero32Float;
                        return;
                    }

                    options.ColorType = TiffColorType.WhiteIsZero32;
                    break;
                }

                case 24:
                {
                    options.ColorType = TiffColorType.WhiteIsZero24;
                    break;
                }

                case 16:
                {
                    options.ColorType = TiffColorType.WhiteIsZero16;
                    break;
                }

                case 8:
                {
                    options.ColorType = TiffColorType.WhiteIsZero8;
                    break;
                }

                case 4:
                {
                    options.ColorType = TiffColorType.WhiteIsZero4;
                    break;
                }

                case 1:
                {
                    options.ColorType = TiffColorType.WhiteIsZero1;
                    break;
                }

                default:
                {
                    options.ColorType = TiffColorType.WhiteIsZero;
                    break;
                }
                }

                break;
            }

            case TiffPhotometricInterpretation.BlackIsZero:
            {
                if (options.BitsPerSample.Channels != 1)
                {
                    TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
                }

                ushort bitsPerChannel = options.BitsPerSample.Channel0;
                if (bitsPerChannel > 32)
                {
                    TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
                }

                switch (bitsPerChannel)
                {
                case 32:
                {
                    if (options.SampleFormat == TiffSampleFormat.Float)
                    {
                        options.ColorType = TiffColorType.BlackIsZero32Float;
                        return;
                    }

                    options.ColorType = TiffColorType.BlackIsZero32;
                    break;
                }

                case 24:
                {
                    options.ColorType = TiffColorType.BlackIsZero24;
                    break;
                }

                case 16:
                {
                    options.ColorType = TiffColorType.BlackIsZero16;
                    break;
                }

                case 8:
                {
                    options.ColorType = TiffColorType.BlackIsZero8;
                    break;
                }

                case 4:
                {
                    options.ColorType = TiffColorType.BlackIsZero4;
                    break;
                }

                case 1:
                {
                    options.ColorType = TiffColorType.BlackIsZero1;
                    break;
                }

                default:
                {
                    options.ColorType = TiffColorType.BlackIsZero;
                    break;
                }
                }

                break;
            }

            case TiffPhotometricInterpretation.Rgb:
            {
                TiffBitsPerSample bitsPerSample = options.BitsPerSample;
                if (bitsPerSample.Channels != 3)
                {
                    TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
                }

                if (!(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2))
                {
                    TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported.");
                }

                if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
                {
                    ushort bitsPerChannel = options.BitsPerSample.Channel0;
                    switch (bitsPerChannel)
                    {
                    case 32:
                        if (options.SampleFormat == TiffSampleFormat.Float)
                        {
                            options.ColorType = TiffColorType.RgbFloat323232;
                            return;
                        }

                        options.ColorType = TiffColorType.Rgb323232;
                        break;

                    case 24:
                        options.ColorType = TiffColorType.Rgb242424;
                        break;

                    case 16:
                        options.ColorType = TiffColorType.Rgb161616;
                        break;

                    case 14:
                        options.ColorType = TiffColorType.Rgb141414;
                        break;

                    case 12:
                        options.ColorType = TiffColorType.Rgb121212;
                        break;

                    case 10:
                        options.ColorType = TiffColorType.Rgb101010;
                        break;

                    case 8:
                        options.ColorType = TiffColorType.Rgb888;
                        break;

                    case 4:
                        options.ColorType = TiffColorType.Rgb444;
                        break;

                    case 2:
                        options.ColorType = TiffColorType.Rgb222;
                        break;

                    default:
                        TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
                        break;
                    }
                }
                else
                {
                    ushort bitsPerChannel = options.BitsPerSample.Channel0;
                    switch (bitsPerChannel)
                    {
                    case 32:
                        options.ColorType = TiffColorType.Rgb323232Planar;
                        break;

                    case 24:
                        options.ColorType = TiffColorType.Rgb242424Planar;
                        break;

                    case 16:
                        options.ColorType = TiffColorType.Rgb161616Planar;
                        break;

                    default:
                        options.ColorType = TiffColorType.Rgb888Planar;
                        break;
                    }
                }

                break;
            }

            case TiffPhotometricInterpretation.PaletteColor:
            {
                options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
                if (options.ColorMap != null)
                {
                    if (options.BitsPerSample.Channels != 1)
                    {
                        TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
                    }

                    options.ColorType = TiffColorType.PaletteColor;
                }
                else
                {
                    TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image.");
                }

                break;
            }

            case TiffPhotometricInterpretation.YCbCr:
            {
                options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
                if (options.BitsPerSample.Channels != 3)
                {
                    TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
                }

                ushort bitsPerChannel = options.BitsPerSample.Channel0;
                if (bitsPerChannel != 8)
                {
                    TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for YCbCr images.");
                }

                options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.YCbCr : TiffColorType.YCbCrPlanar;

                break;
            }

            default:
            {
                TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}");
            }

            break;
            }
        }