From 34ae604b29aa523fc6b4dc3efe1a42233fbf03ba Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 4 Nov 2022 20:17:06 +0100 Subject: [PATCH] Add support for decoding tiled tiff images --- src/ImageSharp/Formats/Tiff/README.md | 22 +- .../Formats/Tiff/TiffDecoderCore.cs | 395 ++++++++++++++---- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 63 ++- .../Profiles/Exif/Tags/ExifTag.LongArray.cs | 5 - .../Profiles/Exif/Tags/ExifTag.NumberArray.cs | 5 + .../Profiles/Exif/Values/ExifValues.cs | 4 +- 6 files changed, 378 insertions(+), 116 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 99594b4c5..8f4cd8363 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -46,18 +46,20 @@ ### Photometric Interpretation Formats -| |Encoder|Decoder|Comments | -|---------------------------|:-----:|:-----:|-------------------------------------------| +| |Encoder|Decoder|Comments | +|---------------------------|:-----:|:-----:|------------------------------------------------| |WhiteIsZero | Y | Y | General + 1/4/8-bit optimised implementations. | |BlackIsZero | Y | Y | General + 1/4/8-bit optimised implementations. | -|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation.| -|Rgb (Planar) | | Y | General implementation only. | -|PaletteColor | Y | Y | General implementation only. | -|TransparencyMask | | | | -|Separated (TIFF Extension) | | Y | | -|YCbCr (TIFF Extension) | | Y | | -|CieLab (TIFF Extension) | | Y | | -|IccLab (TechNote 1) | | | | +|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation. | +|Rgb (Planar) | | Y | General implementation only. | +|PaletteColor | Y | Y | General implementation only. | +|TransparencyMask | | | | +|Separated (TIFF Extension) | | Y | | +|YCbCr (TIFF Extension) | | Y | | +|CieLab (TIFF Extension) | | Y | | +|IccLab (TechNote 1) | | | | +|CMYK | | Y | | +|Tiled Images | | Y | | ### Baseline TIFF Tags diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 59d8b6ecb..032475982 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -214,11 +214,11 @@ internal class TiffDecoderCore : IImageDecoderInternals public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.inputStream = stream; - var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); + DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); IEnumerable directories = reader.Read(); ExifProfile rootFrameExifProfile = directories.First(); - var rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); + TiffFrameMetadata rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, reader.IsBigTiff, rootFrameExifProfile); int width = GetImageWidth(rootFrameExifProfile); @@ -246,13 +246,37 @@ internal class TiffDecoderCore : IImageDecoderInternals TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); TiffFrameMetadata.Parse(tiffFrameMetaData, tags); - this.VerifyAndParse(tags, tiffFrameMetaData); + bool isTiled = this.VerifyAndParse(tags, tiffFrameMetaData); int width = GetImageWidth(tags); int height = GetImageHeight(tags); ImageFrame frame = new(this.configuration, width, height, imageFrameMetaData); - int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; + if (isTiled) + { + this.DecodeImageWithTiles(tags, frame, cancellationToken); + } + else + { + this.DecodeImageWithStrips(tags, frame, cancellationToken); + } + + return frame; + } + + /// + /// Decodes the image data for Tiff's which arrange the pixel data in stripes. + /// + /// The pixel format. + /// The IFD tags. + /// The image frame to decode into. + /// The token to monitor cancellation. + private void DecodeImageWithStrips(ExifProfile tags, ImageFrame frame, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null + ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value + : TiffConstants.RowsPerStripInfinity; Array stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); Array stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); @@ -278,71 +302,35 @@ internal class TiffDecoderCore : IImageDecoderInternals stripByteCounts, cancellationToken); } - - return frame; - } - - private IMemoryOwner ConvertNumbers(Array array, out Span span) - { - if (array is Number[] numbers) - { - IMemoryOwner memory = this.memoryAllocator.Allocate(numbers.Length); - span = memory.GetSpan(); - for (int i = 0; i < numbers.Length; i++) - { - span[i] = (uint)numbers[i]; - } - - return memory; - } - - DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); - span = (ulong[])array; - return null; } /// - /// Calculates the size (in bytes) for a pixel buffer using the determined color format. + /// Decodes the image data for Tiff's which arrange the pixel data in tiles. /// - /// The width for the desired pixel buffer. - /// The height for the desired pixel buffer. - /// The index of the plane for planar image configuration (or zero for chunky). - /// The size (in bytes) of the required pixel buffer. - private int CalculateStripBufferSize(int width, int height, int plane = -1) + /// The pixel format. + /// The IFD tags. + /// The image frame to decode into. + /// The token to monitor cancellation. + private void DecodeImageWithTiles(ExifProfile tags, ImageFrame frame, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); + Buffer2D pixels = frame.PixelBuffer; + int width = pixels.Width; + int height = pixels.Height; - int bitsPerPixel = 0; + int tileWidth = (int)tags.GetValue(ExifTag.TileWidth).Value; + int tileLength = (int)tags.GetValue(ExifTag.TileLength).Value; + int tilesAcross = (width + tileWidth - 1) / tileWidth; + int tilesDown = (height + tileLength - 1) / tileLength; - if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { - DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); - bitsPerPixel = this.BitsPerPixel; + this.DecodeTilesPlanar(tags, frame, tileWidth, tileLength, tilesAcross, tilesDown, cancellationToken); } else { - switch (plane) - { - case 0: - bitsPerPixel = this.BitsPerSample.Channel0; - break; - case 1: - bitsPerPixel = this.BitsPerSample.Channel1; - break; - case 2: - bitsPerPixel = this.BitsPerSample.Channel2; - break; - case 3: - bitsPerPixel = this.BitsPerSample.Channel2; - break; - default: - TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); - break; - } + this.DecodeTilesChunky(tags, frame, tileWidth, tileLength, tilesAcross, tilesDown, cancellationToken); } - - int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; - return bytesPerRow * height; } /// @@ -373,20 +361,7 @@ internal class TiffDecoderCore : IImageDecoderInternals stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); } - using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Options, - this.CompressionType, - this.memoryAllocator, - this.PhotometricInterpretation, - frame.Width, - bitsPerPixel, - this.ColorType, - this.Predictor, - this.FaxCompressionOptions, - this.JpegTables, - this.OldJpegCompressionStartOfImageMarker.GetValueOrDefault(), - this.FillOrder, - this.byteOrder); + using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar( this.ColorType, @@ -455,20 +430,7 @@ internal class TiffDecoderCore : IImageDecoderInternals Span stripBufferSpan = stripBuffer.GetSpan(); Buffer2D pixels = frame.PixelBuffer; - using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Options, - this.CompressionType, - this.memoryAllocator, - this.PhotometricInterpretation, - frame.Width, - bitsPerPixel, - this.ColorType, - this.Predictor, - this.FaxCompressionOptions, - this.JpegTables, - this.OldJpegCompressionStartOfImageMarker.GetValueOrDefault(), - this.FillOrder, - this.byteOrder); + using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( this.configuration, @@ -509,6 +471,269 @@ internal class TiffDecoderCore : IImageDecoderInternals } } + /// + /// Decodes the image data for Tiff's which arrange the pixel data in tiles and the planar configuration. + /// + /// The pixel format. + /// The IFD tags. + /// The image frame to decode into. + /// The width in pixels of the tile. + /// The height in pixels of the tile. + /// The number of tiles horizontally. + /// The number of tiles vertically. + /// The token to monitor cancellation. + private void DecodeTilesPlanar(ExifProfile tags, ImageFrame frame, int tileWidth, int tileLength, int tilesAcross, int tilesDown, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Buffer2D pixels = frame.PixelBuffer; + int width = pixels.Width; + int height = pixels.Height; + int bitsPerPixel = this.BitsPerPixel; + + Array tilesOffsetsArray = (Array)tags.GetValueInternal(ExifTag.TileOffsets).GetValue(); + Array tilesByteCountsArray = (Array)tags.GetValueInternal(ExifTag.TileByteCounts).GetValue(); + using IMemoryOwner tileOffsetsMemory = this.ConvertNumbers(tilesOffsetsArray, out Span tileOffsets); + using IMemoryOwner tileByteCountsMemory = this.ConvertNumbers(tilesByteCountsArray, out Span tileByteCounts); + + int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; + int bytesPerTileRow = ((tileWidth * bitsPerPixel) + 7) / 8; + int uncompressedTilesSize = bytesPerTileRow * tileLength; + using IMemoryOwner tileBuffer = this.memoryAllocator.Allocate(uncompressedTilesSize, AllocationOptions.Clean); + using IMemoryOwner uncompressedPixelBuffer = this.memoryAllocator.Allocate(tilesDown * tileLength * bytesPerRow, AllocationOptions.Clean); + Span tileBufferSpan = tileBuffer.GetSpan(); + Span uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan(); + + using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); + + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( + this.configuration, + this.memoryAllocator, + this.ColorType, + this.BitsPerSample, + this.ExtraSamplesType, + this.ColorMap, + this.ReferenceBlackAndWhite, + this.YcbcrCoefficients, + this.YcbcrSubSampling, + this.byteOrder); + + int tileIndex = 0; + for (int tileY = 0; tileY < tilesDown; tileY++) + { + int uncompressedPixelBufferOffset = tileY * tileLength * bytesPerRow; + int remainingPixelsInRow = width; + for (int tileX = 0; tileX < tilesAcross; tileX++) + { + cancellationToken.ThrowIfCancellationRequested(); + + bool isLastHorizontalTile = tileX == tilesAcross - 1; + + decompressor.Decompress( + this.inputStream, + tileOffsets[tileIndex], + tileByteCounts[tileIndex], + tileLength, + tileBufferSpan, + cancellationToken); + + int tileBufferOffset = 0; + uncompressedPixelBufferOffset += bytesPerTileRow * tileX; + int bytesToCopy = isLastHorizontalTile ? ((bitsPerPixel * remainingPixelsInRow) + 7) / 8 : bytesPerTileRow; + for (int y = 0; y < tileLength; y++) + { + Span uncompressedPixelRow = uncompressedPixelBufferSpan.Slice(uncompressedPixelBufferOffset, bytesToCopy); + tileBufferSpan.Slice(tileBufferOffset, bytesToCopy).CopyTo(uncompressedPixelRow); + tileBufferOffset += bytesPerTileRow; + uncompressedPixelBufferOffset += bytesPerRow; + } + + remainingPixelsInRow -= tileWidth; + tileIndex++; + } + } + + colorDecoder.Decode(uncompressedPixelBufferSpan, pixels, 0, 0, width, height); + } + + /// + /// Decodes the image data for Tiff's which arrange the pixel data in tiles and the chunky configuration. + /// + /// The pixel format. + /// The IFD tags. + /// The image frame to decode into. + /// The width in pixels of the tile. + /// The height in pixels of the tile. + /// The number of tiles horizontally. + /// The number of tiles vertically. + /// The token to monitor cancellation. + private void DecodeTilesChunky(ExifProfile tags, ImageFrame frame, int tileWidth, int tileLength, int tilesAcross, int tilesDown, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Buffer2D pixels = frame.PixelBuffer; + int width = pixels.Width; + int height = pixels.Height; + int bitsPerPixel = this.BitsPerPixel; + + Array tilesOffsetsArray; + Array tilesByteCountsArray; + IExifValue tilesOffsetsExifValue = tags.GetValueInternal(ExifTag.TileOffsets); + IExifValue tilesByteCountsExifValue = tags.GetValueInternal(ExifTag.TileByteCounts); + if (tilesOffsetsExifValue is null) + { + tilesOffsetsExifValue = tags.GetValueInternal(ExifTag.StripOffsets); + tilesByteCountsExifValue = tags.GetValueInternal(ExifTag.StripByteCounts); + tilesOffsetsArray = (Array)tilesOffsetsExifValue.GetValue(); + tilesByteCountsArray = (Array)tilesByteCountsExifValue.GetValue(); + } + else + { + tilesOffsetsArray = (Array)tilesOffsetsExifValue.GetValue(); + tilesByteCountsArray = (Array)tilesByteCountsExifValue.GetValue(); + } + + using IMemoryOwner tileOffsetsMemory = this.ConvertNumbers(tilesOffsetsArray, out Span tileOffsets); + using IMemoryOwner tileByteCountsMemory = this.ConvertNumbers(tilesByteCountsArray, out Span tileByteCounts); + + int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; + int bytesPerTileRow = ((tileWidth * bitsPerPixel) + 7) / 8; + int uncompressedTilesSize = bytesPerTileRow * tileLength; + using IMemoryOwner tileBuffer = this.memoryAllocator.Allocate(uncompressedTilesSize, AllocationOptions.Clean); + using IMemoryOwner uncompressedPixelBuffer = this.memoryAllocator.Allocate(tilesDown * tileLength * bytesPerRow, AllocationOptions.Clean); + Span tileBufferSpan = tileBuffer.GetSpan(); + Span uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan(); + + using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); + + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( + this.configuration, + this.memoryAllocator, + this.ColorType, + this.BitsPerSample, + this.ExtraSamplesType, + this.ColorMap, + this.ReferenceBlackAndWhite, + this.YcbcrCoefficients, + this.YcbcrSubSampling, + this.byteOrder); + + int tileIndex = 0; + for (int tileY = 0; tileY < tilesDown; tileY++) + { + int remainingPixelsInRow = width; + for (int tileX = 0; tileX < tilesAcross; tileX++) + { + cancellationToken.ThrowIfCancellationRequested(); + + int uncompressedPixelBufferOffset = tileY * tileLength * bytesPerRow; + bool isLastHorizontalTile = tileX == tilesAcross - 1; + + decompressor.Decompress( + this.inputStream, + tileOffsets[tileIndex], + tileByteCounts[tileIndex], + tileLength, + tileBufferSpan, + cancellationToken); + + int tileBufferOffset = 0; + uncompressedPixelBufferOffset += bytesPerTileRow * tileX; + int bytesToCopy = isLastHorizontalTile ? ((bitsPerPixel * remainingPixelsInRow) + 7) / 8 : bytesPerTileRow; + for (int y = 0; y < tileLength; y++) + { + Span uncompressedPixelRow = uncompressedPixelBufferSpan.Slice(uncompressedPixelBufferOffset, bytesToCopy); + tileBufferSpan.Slice(tileBufferOffset, bytesToCopy).CopyTo(uncompressedPixelRow); + tileBufferOffset += bytesPerTileRow; + uncompressedPixelBufferOffset += bytesPerRow; + } + + remainingPixelsInRow -= tileWidth; + tileIndex++; + } + } + + colorDecoder.Decode(uncompressedPixelBufferSpan, pixels, 0, 0, width, height); + } + + private TiffBaseDecompressor CreateDecompressor(int frameWidth, int bitsPerPixel) + where TPixel : unmanaged, IPixel => + TiffDecompressorsFactory.Create( + this.Options, + this.CompressionType, + this.memoryAllocator, + this.PhotometricInterpretation, + frameWidth, + bitsPerPixel, + this.ColorType, + this.Predictor, + this.FaxCompressionOptions, + this.JpegTables, + this.OldJpegCompressionStartOfImageMarker.GetValueOrDefault(), + this.FillOrder, + this.byteOrder); + + private IMemoryOwner ConvertNumbers(Array array, out Span span) + { + if (array is Number[] numbers) + { + IMemoryOwner memory = this.memoryAllocator.Allocate(numbers.Length); + span = memory.GetSpan(); + for (int i = 0; i < numbers.Length; i++) + { + span[i] = (uint)numbers[i]; + } + + return memory; + } + + DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); + span = (ulong[])array; + return null; + } + + /// + /// Calculates the size (in bytes) for a pixel buffer using the determined color format. + /// + /// The width for the desired pixel buffer. + /// The height for the desired pixel buffer. + /// The index of the plane for planar image configuration (or zero for chunky). + /// The size (in bytes) of the required pixel buffer. + 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; + case 3: + bitsPerPixel = this.BitsPerSample.Channel2; + break; + default: + TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); + break; + } + } + + int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; + return bytesPerRow * height; + } + /// /// Gets the width of the image frame. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 5a4baafa4..384e2389c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -21,13 +21,9 @@ internal static class TiffDecoderOptionsParser /// The options. /// The exif profile of the frame to decode. /// The IFD entries container to read the image format information for current frame. - public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + /// True, if the image uses tiles. Otherwise the images has strip's. + public static bool 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."); - } - IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples); if (extraSamplesExifValue is not null) { @@ -86,8 +82,6 @@ internal static class TiffDecoderOptionsParser 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; @@ -103,24 +97,65 @@ internal static class TiffDecoderOptionsParser options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); + + bool isTiled = VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration); + + return isTiled; } - private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + /// + /// Verifies that all required fields for decoding are present. + /// + /// The exif profile. + /// The frame metadata. + /// The planar configuration. Either planar or chunky. + /// True, if the image uses tiles. Otherwise the images has strip's. + private static bool VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata, TiffPlanarConfiguration planarConfiguration) { - if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) + bool isTiled = false; + if (exifProfile.GetValueInternal(ExifTag.TileWidth) is not null || exifProfile.GetValueInternal(ExifTag.TileLength) is not null) { - TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); - } + if (planarConfiguration == TiffPlanarConfiguration.Planar && exifProfile.GetValueInternal(ExifTag.TileOffsets) is null) + { + TiffThrowHelper.ThrowImageFormatException("TileOffsets are missing and are required for decoding the TIFF image!"); + } + + if (planarConfiguration == TiffPlanarConfiguration.Chunky && exifProfile.GetValueInternal(ExifTag.TileOffsets) is null && exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) + { + TiffThrowHelper.ThrowImageFormatException("TileOffsets are missing and are required for decoding the TIFF image!"); + } - if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null) + if (exifProfile.GetValueInternal(ExifTag.TileWidth) is null) + { + TiffThrowHelper.ThrowImageFormatException("TileWidth are missing and are required for decoding the TIFF image!"); + } + + if (exifProfile.GetValueInternal(ExifTag.TileLength) is null) + { + TiffThrowHelper.ThrowImageFormatException("TileLength are missing and are required for decoding the TIFF image!"); + } + + isTiled = true; + } + else { - TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); + 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!"); } + + return isTiled; } private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs index 29de86943..741df50f2 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs @@ -21,11 +21,6 @@ public abstract partial class ExifTag /// public static ExifTag ColorResponseUnit { get; } = new ExifTag(ExifTagValue.ColorResponseUnit); - /// - /// Gets the TileOffsets exif tag. - /// - public static ExifTag TileOffsets { get; } = new ExifTag(ExifTagValue.TileOffsets); - /// /// Gets the SMinSampleValue exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs index ecd4d87d5..cd8968141 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs @@ -21,6 +21,11 @@ public abstract partial class ExifTag /// public static ExifTag TileByteCounts { get; } = new ExifTag(ExifTagValue.TileByteCounts); + /// + /// Gets the TileOffsets exif tag. + /// + public static ExifTag TileOffsets { get; } = new ExifTag(ExifTagValue.TileOffsets); + /// /// Gets the ImageLayer exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index 93720fbe9..2302a6681 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -130,8 +130,6 @@ internal static partial class ExifValues return new ExifLongArray(ExifTag.FreeByteCounts); case ExifTagValue.ColorResponseUnit: return new ExifLongArray(ExifTag.ColorResponseUnit); - case ExifTagValue.TileOffsets: - return new ExifLongArray(ExifTag.TileOffsets); case ExifTagValue.SMinSampleValue: return new ExifLongArray(ExifTag.SMinSampleValue); case ExifTagValue.SMaxSampleValue: @@ -176,6 +174,8 @@ internal static partial class ExifValues return new ExifNumberArray(ExifTag.StripOffsets); case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); + case ExifTagValue.TileOffsets: + return new ExifNumberArray(ExifTag.TileOffsets); case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer);