Browse Source

Add support for decoding tiled tiff images

pull/2290/head
Brian Popow 3 years ago
parent
commit
34ae604b29
  1. 22
      src/ImageSharp/Formats/Tiff/README.md
  2. 395
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  3. 63
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  4. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
  5. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs
  6. 4
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs

22
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

395
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<ExifProfile> 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<TPixel> 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;
}
/// <summary>
/// Decodes the image data for Tiff's which arrange the pixel data in stripes.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="frame">The image frame to decode into.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
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<ulong> ConvertNumbers(Array array, out Span<ulong> span)
{
if (array is Number[] numbers)
{
IMemoryOwner<ulong> memory = this.memoryAllocator.Allocate<ulong>(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;
}
/// <summary>
/// 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.
/// </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)
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="frame">The image frame to decode into.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeImageWithTiles<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));
Buffer2D<TPixel> 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;
}
/// <summary>
@ -373,20 +361,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(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<TPixel>(frame.Width, bitsPerPixel);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(
this.ColorType,
@ -455,20 +430,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
Buffer2D<TPixel> 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<TPixel>(frame.Width, bitsPerPixel);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(
this.configuration,
@ -509,6 +471,269 @@ internal class TiffDecoderCore : IImageDecoderInternals
}
}
/// <summary>
/// Decodes the image data for Tiff's which arrange the pixel data in tiles and the planar configuration.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="frame">The image frame to decode into.</param>
/// <param name="tileWidth">The width in pixels of the tile.</param>
/// <param name="tileLength">The height in pixels of the tile.</param>
/// <param name="tilesAcross">The number of tiles horizontally.</param>
/// <param name="tilesDown">The number of tiles vertically.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeTilesPlanar<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, int tileWidth, int tileLength, int tilesAcross, int tilesDown, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> 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<ulong> tileOffsetsMemory = this.ConvertNumbers(tilesOffsetsArray, out Span<ulong> tileOffsets);
using IMemoryOwner<ulong> tileByteCountsMemory = this.ConvertNumbers(tilesByteCountsArray, out Span<ulong> tileByteCounts);
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
int bytesPerTileRow = ((tileWidth * bitsPerPixel) + 7) / 8;
int uncompressedTilesSize = bytesPerTileRow * tileLength;
using IMemoryOwner<byte> tileBuffer = this.memoryAllocator.Allocate<byte>(uncompressedTilesSize, AllocationOptions.Clean);
using IMemoryOwner<byte> uncompressedPixelBuffer = this.memoryAllocator.Allocate<byte>(tilesDown * tileLength * bytesPerRow, AllocationOptions.Clean);
Span<byte> tileBufferSpan = tileBuffer.GetSpan();
Span<byte> uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan();
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.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<byte> 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);
}
/// <summary>
/// Decodes the image data for Tiff's which arrange the pixel data in tiles and the chunky configuration.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="frame">The image frame to decode into.</param>
/// <param name="tileWidth">The width in pixels of the tile.</param>
/// <param name="tileLength">The height in pixels of the tile.</param>
/// <param name="tilesAcross">The number of tiles horizontally.</param>
/// <param name="tilesDown">The number of tiles vertically.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeTilesChunky<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, int tileWidth, int tileLength, int tilesAcross, int tilesDown, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> 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<ulong> tileOffsetsMemory = this.ConvertNumbers(tilesOffsetsArray, out Span<ulong> tileOffsets);
using IMemoryOwner<ulong> tileByteCountsMemory = this.ConvertNumbers(tilesByteCountsArray, out Span<ulong> tileByteCounts);
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
int bytesPerTileRow = ((tileWidth * bitsPerPixel) + 7) / 8;
int uncompressedTilesSize = bytesPerTileRow * tileLength;
using IMemoryOwner<byte> tileBuffer = this.memoryAllocator.Allocate<byte>(uncompressedTilesSize, AllocationOptions.Clean);
using IMemoryOwner<byte> uncompressedPixelBuffer = this.memoryAllocator.Allocate<byte>(tilesDown * tileLength * bytesPerRow, AllocationOptions.Clean);
Span<byte> tileBufferSpan = tileBuffer.GetSpan();
Span<byte> uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan();
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.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<byte> 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<TPixel>(int frameWidth, int bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel> =>
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<ulong> ConvertNumbers(Array array, out Span<ulong> span)
{
if (array is Number[] numbers)
{
IMemoryOwner<ulong> memory = this.memoryAllocator.Allocate<ulong>(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;
}
/// <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;
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;
}
/// <summary>
/// Gets the width of the image frame.
/// </summary>

63
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -21,13 +21,9 @@ internal static class TiffDecoderOptionsParser
/// <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)
/// <returns>True, if the image uses tiles. Otherwise the images has strip's.</returns>
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)
/// <summary>
/// Verifies that all required fields for decoding are present.
/// </summary>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="frameMetadata">The frame metadata.</param>
/// <param name="planarConfiguration">The planar configuration. Either planar or chunky.</param>
/// <returns>True, if the image uses tiles. Otherwise the images has strip's.</returns>
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)

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs

@ -21,11 +21,6 @@ public abstract partial class ExifTag
/// </summary>
public static ExifTag<uint[]> ColorResponseUnit { get; } = new ExifTag<uint[]>(ExifTagValue.ColorResponseUnit);
/// <summary>
/// Gets the TileOffsets exif tag.
/// </summary>
public static ExifTag<uint[]> TileOffsets { get; } = new ExifTag<uint[]>(ExifTagValue.TileOffsets);
/// <summary>
/// Gets the SMinSampleValue exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs

@ -21,6 +21,11 @@ public abstract partial class ExifTag
/// </summary>
public static ExifTag<Number[]> TileByteCounts { get; } = new ExifTag<Number[]>(ExifTagValue.TileByteCounts);
/// <summary>
/// Gets the TileOffsets exif tag.
/// </summary>
public static ExifTag<Number[]> TileOffsets { get; } = new ExifTag<Number[]>(ExifTagValue.TileOffsets);
/// <summary>
/// Gets the ImageLayer exif tag.
/// </summary>

4
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);

Loading…
Cancel
Save