diff --git a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs index e10d8195b..3e07851d4 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff if ((cmf & 0x0f) != 8) { - throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); + TiffThrowHelper.ThrowBadZlibHeader(cmf); } // If the 'fdict' flag is set then we should skip the next four bytes diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs index 7e077983d..0f893448d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffCompressionType.Lzw: return new LzwTiffCompression(allocator); default: - throw new InvalidOperationException(); + throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType)); } } } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 693b3abfc..3aedf422b 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Tiff @@ -40,13 +38,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff ushort magic = this.stream.ReadUInt16(); if (magic != TiffConstants.HeaderMagicNumber) { - throw new ImageFormatException("Invalid TIFF header magic number: " + magic); + TiffThrowHelper.ThrowInvalidHeader(); } uint firstIfdOffset = this.stream.ReadUInt32(); if (firstIfdOffset == 0) { - throw new ImageFormatException("Invalid TIFF file header."); + TiffThrowHelper.ThrowInvalidHeader(); } this.nextIfdOffset = firstIfdOffset; @@ -95,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else if (leftBytes < 0) { - throw new InvalidDataException("Out of range of IFD structure."); + TiffThrowHelper.ThrowOutOfRange("IFD"); } return entries.ToArray(); diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index b605a8737..57d69b4a8 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else if (leftBytes < 0) { - throw new InvalidDataException("Out of range of IFD entry structure."); + TiffThrowHelper.ThrowOutOfRange("IFD entry"); } } @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (buf[buf.Length - 1] != 0) { - throw new ImageFormatException("The retrieved string is not null terminated."); + TiffThrowHelper.ThrowBadStringEntry(); } return Encoding.UTF8.GetString(buf, 0, buf.Length - 1); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs index a01a25e8b..20129da99 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new PaletteTiffColor(bitsPerSample, colorMap); default: - throw new InvalidOperationException(); + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } } @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new RgbPlanarTiffColor(bitsPerSample); default: - throw new InvalidOperationException(); + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } } } diff --git a/src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs b/src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs deleted file mode 100644 index f65062d59..000000000 --- a/src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - /// - /// The tiff data stream factory class. - /// - internal static class TiffStreamFactory - { - /// - /// Creates the specified byte order. - /// - /// The byte order. - /// The stream. - public static TiffStream Create(TiffByteOrder byteOrder, Stream stream) - { - if (byteOrder == TiffByteOrder.BigEndian) - { - return new TiffBigEndianStream(stream); - } - else if (byteOrder == TiffByteOrder.LittleEndian) - { - return new TiffLittleEndianStream(stream); - } - - throw new ArgumentOutOfRangeException(nameof(byteOrder)); - } - - /// - /// Reads the byte order of stream. - /// - /// The stream. - public static TiffByteOrder ReadByteOrder(Stream stream) - { - byte[] headerBytes = new byte[2]; - stream.Read(headerBytes, 0, 2); - if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) - { - return TiffByteOrder.LittleEndian; - } - else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) - { - return TiffByteOrder.BigEndian; - } - - throw new ImageFormatException("Invalid TIFF file header."); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index aed21af56..fadcb7550 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { Guard.NotNull(stream, "stream"); - using var decoder = new TiffDecoderCore(stream, configuration, this); + var decoder = new TiffDecoderCore(configuration, this); return decoder.Decode(configuration, stream); } @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { Guard.NotNull(stream, nameof(stream)); - var decoder = new TiffDecoderCore(stream, configuration, this); + var decoder = new TiffDecoderCore(configuration, this); return decoder.DecodeAsync(configuration, stream, cancellationToken); } @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { Guard.NotNull(stream, nameof(stream)); - var decoder = new TiffDecoderCore(stream, configuration, this); + var decoder = new TiffDecoderCore(configuration, this); return decoder.Identify(configuration, stream); } @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { Guard.NotNull(stream, nameof(stream)); - var decoder = new TiffDecoderCore(stream, configuration, this); + var decoder = new TiffDecoderCore(configuration, this); return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 947110137..468989d19 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Performs the tiff decoding operation. /// - internal class TiffDecoderCore : IImageDecoderInternals, IDisposable + internal class TiffDecoderCore : IImageDecoderInternals { /// /// The global configuration @@ -35,42 +35,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// private readonly bool ignoreMetadata; + private BufferedReadStream inputStream; + /// /// Initializes a new instance of the class. /// /// The configuration. /// The decoder options. - private TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) + public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) { - options = options ?? new TiffDecoder(); + options ??= new TiffDecoder(); this.configuration = configuration ?? Configuration.Default; this.ignoreMetadata = options.IgnoreMetadata; this.memoryAllocator = this.configuration.MemoryAllocator; } - /// - /// Initializes a new instance of the class. - /// - /// The stream. - /// The configuration. - /// The decoder options. - public TiffDecoderCore(Stream stream, Configuration configuration, ITiffDecoderOptions options) - : this(configuration, options) - { - this.ByteOrder = TiffStreamFactory.ReadByteOrder(stream); - } - - /// - /// Gets the byte order. - /// - public TiffByteOrder ByteOrder { get; } - - /// - /// Gets the input stream. - /// - public TiffStream Stream { get; private set; } - /// /// Gets or sets the number of bits for each sample of the pixel format used to encode the image. /// @@ -111,8 +91,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream); - var reader = new DirectoryReader(this.Stream); + this.inputStream = stream; + TiffStream tiffStream = CreateStream(stream); + var reader = new DirectoryReader(tiffStream); IEnumerable directories = reader.Read(); @@ -125,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff framesMetadata.Add(frameMetadata); } - ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder); + ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder); // todo: tiff frames can have different sizes { @@ -135,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (frame.Size() != root.Size()) { - throw new NotSupportedException("Images with different sizes are not supported"); + TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); } } } @@ -148,8 +129,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream); - var reader = new DirectoryReader(this.Stream); + this.inputStream = stream; + TiffStream tiffStream = CreateStream(stream); + var reader = new DirectoryReader(tiffStream); IEnumerable directories = reader.Read(); @@ -159,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff framesMetadata.Add(new TiffFrameMetadata() { Tags = ifd }); } - ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder); + ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder); TiffFrameMetadata root = framesMetadata.First(); int bitsPerPixel = 0; @@ -171,10 +153,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, metadata); } - /// - public void Dispose() + private static TiffStream CreateStream(Stream stream) + { + TiffByteOrder byteOrder = ReadByteOrder(stream); + if (byteOrder == TiffByteOrder.BigEndian) + { + return new TiffBigEndianStream(stream); + } + else if (byteOrder == TiffByteOrder.LittleEndian) + { + return new TiffLittleEndianStream(stream); + } + + throw TiffThrowHelper.InvalidHeader(); + } + + private static TiffByteOrder ReadByteOrder(Stream stream) { - // nothing + byte[] headerBytes = new byte[2]; + stream.Read(headerBytes, 0, 2); + if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) + { + return TiffByteOrder.LittleEndian; + } + else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) + { + return TiffByteOrder.BigEndian; + } + + throw TiffThrowHelper.InvalidHeader(); } /// @@ -283,8 +290,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { int stripIndex = (i * stripsPerPixel) + planeIndex; - this.Stream.Seek(stripOffsets[stripIndex]); - decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); + this.inputStream.Seek(stripOffsets[stripIndex], SeekOrigin.Begin); + decompressor.Decompress(this.inputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); } colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); @@ -316,8 +323,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; - this.Stream.Seek(stripOffsets[stripIndex]); - decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan()); + this.inputStream.Seek(stripOffsets[stripIndex], SeekOrigin.Begin); + decompressor.Decompress(this.inputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan()); colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, rowsPerStrip * stripIndex, frame.Width, stripHeight); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs index ebfdf5df0..86a7560cf 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs @@ -92,22 +92,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (entries.ExtraSamples != null) { - throw new NotSupportedException("ExtraSamples is not supported."); + TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); } if (entries.FillOrder != TiffFillOrder.MostSignificantBitFirst) { - throw new NotSupportedException("The lower-order bits of the byte FillOrder is not supported."); + TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported."); } if (entries.GetArray(ExifTag.TileOffsets, true) != null) { - throw new NotSupportedException("The Tile images is not supported."); + TiffThrowHelper.ThrowNotSupported("The Tile images is not supported."); } if (entries.Predictor != TiffPredictor.None) { - throw new NotSupportedException("At the moment support only None Predictor."); + TiffThrowHelper.ThrowNotSupported("At the moment support only None Predictor."); } if (entries.SampleFormat != null) @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (format != TiffSampleFormat.UnsignedInteger) { - throw new NotSupportedException("At the moment support only UnsignedInteger SampleFormat."); + TiffThrowHelper.ThrowNotSupported("At the moment support only UnsignedInteger SampleFormat."); } } } @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } break; @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } break; @@ -236,7 +236,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } break; @@ -260,19 +260,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } } else { - throw new ImageFormatException("The TIFF ColorMap entry is missing for a palette color image."); + TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image."); } break; } default: - throw new NotSupportedException("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation); + { + TiffThrowHelper.ThrowNotSupported("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation); + } + + break; } } @@ -288,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - throw new ImageFormatException("The TIFF BitsPerSample entry is missing."); + TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing."); } } } @@ -304,7 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - throw new ImageFormatException("The TIFF photometric interpretation entry is missing."); + TiffThrowHelper.ThrowNotSupported("The TIFF photometric interpretation entry is missing."); } } @@ -346,7 +350,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff default: { - throw new NotSupportedException("The specified TIFF compression format is not supported: " + compression); + TiffThrowHelper.ThrowNotSupported("The specified TIFF compression format is not supported: " + compression); + break; } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index cfec448a4..466702693 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -209,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (!optional) { - throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)); + TiffThrowHelper.ThrowTagNotFound(nameof(tag)); } return null; @@ -245,7 +245,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (!optional) { - throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)); + TiffThrowHelper.ThrowTagNotFound(nameof(tag)); } return null; @@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff private TEnum GetSingleEnum(ExifTag tag, TEnum? defaultValue = null) where TEnum : struct where TTagValue : struct - => this.GetSingleEnumNullable(tag) ?? (defaultValue != null ? defaultValue.Value : throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag))); + => this.GetSingleEnumNullable(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag))); private T GetSingle(ExifTag tag) where T : struct @@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff return result; } - throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)); + throw TiffThrowHelper.TagNotFound(nameof(tag)); } private bool TryGetSingle(ExifTag tag, out T result) diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs new file mode 100644 index 000000000..3254744bc --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Cold path optimizations for throwing tiff format based exceptions. + /// + internal static class TiffThrowHelper + { + [MethodImpl(InliningOptions.ColdPath)] + public static Exception TagNotFound(string tagName) + => new ArgumentException("Required tag is not found.", tagName); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowTagNotFound(string tagName) + => throw TagNotFound(tagName); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadZlibHeader(int cmf) => throw new ImageFormatException($"Bad compression method for ZLIB header: cmf={cmf}"); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedCompression(string compressionType) => new NotSupportedException("Not supported compression: " + compressionType); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedCompression(string compressionType) => throw NotSupportedCompression(compressionType); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception InvalidColorType(string colorType) => new NotSupportedException("Invalid color type: " + colorType); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception InvalidHeader() => new ImageFormatException("Invalid TIFF file header."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidHeader() => throw InvalidHeader(); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowOutOfRange(string structure) => throw new InvalidDataException($"Out of range of {structure} structure."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadStringEntry() => throw new ImageFormatException("The retrieved string is not null terminated."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); + } +}