@ -167,11 +167,18 @@ internal class TiffDecoderCore : ImageDecoderCore
this . byteOrder = reader . ByteOrder ;
this . isBigTiff = reader . IsBigTiff ;
Size ? size = null ;
uint frameCount = 0 ;
foreach ( ExifProfile ifd in directories )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
ImageFrame < TPixel > frame = this . DecodeFrame < TPixel > ( ifd , cancellationToken ) ;
ImageFrame < TPixel > frame = this . DecodeFrame < TPixel > ( ifd , size , cancellationToken ) ;
if ( ! size . HasValue )
{
size = frame . Size ;
}
frames . Add ( frame ) ;
framesMetadata . Add ( frame . Metadata ) ;
@ -181,19 +188,8 @@ internal class TiffDecoderCore : ImageDecoderCore
}
}
this . Dimensions = frames [ 0 ] . Size ;
ImageMetadata metadata = TiffDecoderMetadataCreator . Create ( framesMetadata , this . skipMetadata , reader . ByteOrder , reader . IsBigTiff ) ;
// 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 ) ;
}
catch
@ -215,17 +211,21 @@ internal class TiffDecoderCore : ImageDecoderCore
IList < ExifProfile > directories = reader . Read ( ) ;
List < ImageFrameMetadata > framesMetadata = [ ] ;
foreach ( ExifProfile dir in directories )
int width = 0 ;
int height = 0 ;
for ( int i = 0 ; i < directories . Count ; i + + )
{
framesMetadata . Add ( this . CreateFrameMetadata ( dir ) ) ;
}
( ImageFrameMetadata FrameMetadata , TiffFrameMetadata TiffMetadata ) meta
= this . CreateFrameMetadata ( directories [ i ] ) ;
ExifProfile rootFrameExifProfile = directories [ 0 ] ;
framesMetadata . Add ( meta . FrameMetadata ) ;
ImageMetadata metadata = TiffDecoderMetadataCreator . Create ( framesMetadata , this . skipMetadata , reader . ByteOrder , reader . IsBigTiff ) ;
width = Math . Max ( width , meta . TiffMetadata . EncodingWidth ) ;
height = Math . Max ( height , meta . TiffMetadata . EncodingHeight ) ;
}
int width = GetImageWidth ( rootFrameExifProfile ) ;
int height = GetImageHeight ( rootFrameExifProfile ) ;
ImageMetadata metadata = TiffDecoderMetadataCreator . Create ( framesMetadata , this . skipMetadata , reader . ByteOrder , reader . IsBigTiff ) ;
return new ImageInfo ( new ( width , height ) , metadata , framesMetadata ) ;
}
@ -235,31 +235,46 @@ internal class TiffDecoderCore : ImageDecoderCore
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="size">The previously determined root frame size if decoded.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
/// <returns>The tiff frame.</returns>
private ImageFrame < TPixel > DecodeFrame < TPixel > ( ExifProfile tags , CancellationToken cancellationToken )
private ImageFrame < TPixel > DecodeFrame < TPixel > ( ExifProfile tags , Size ? size , CancellationToken cancellationToken )
where TPixel : unmanaged , IPixel < TPixel >
{
ImageFrameMetadata imageFrameMetaData = this . CreateFrameMetadata ( tags ) ;
bool isTiled = this . VerifyAndParse ( tags , imageFrameMetaData . GetTiffMetadata ( ) ) ;
( ImageFrameMetadata FrameMetadata , TiffFrameMetadata TiffFrameMetadata ) metadata = this . CreateFrameMetadata ( tags ) ;
bool isTiled = this . VerifyAndParse ( tags , metadata . TiffFrameMetadata ) ;
int width = metadata . TiffFrameMetadata . EncodingWidth ;
int height = metadata . TiffFrameMetadata . EncodingHeight ;
// If size has a value and the width/height off the tiff is smaller we much capture the delta.
if ( size . HasValue )
{
if ( size . Value . Width < width | | size . Value . Height < height )
{
TiffThrowHelper . ThrowNotSupported ( "Images with frames of size greater than the root frame are not supported." ) ;
}
}
else
{
size = new Size ( width , height ) ;
}
int width = GetImageWidth ( tags ) ;
int height = GetImageHeight ( tags ) ;
ImageFrame < TPixel > frame = new ( this . configuration , width , height , imageFrameMetaData ) ;
ImageFrame < TPixel > frame = new ( this . configuration , size . Value . Width , size . Value . Height , metadata . FrameMetadata ) ;
if ( isTiled )
{
this . DecodeImageWithTiles ( tags , frame , cancellationToken ) ;
this . DecodeImageWithTiles ( tags , frame , width , height , cancellationToken ) ;
}
else
{
this . DecodeImageWithStrips ( tags , frame , cancellationToken ) ;
this . DecodeImageWithStrips ( tags , frame , width , height , cancellationToken ) ;
}
return frame ;
}
private ImageFrameMetadata CreateFrameMetadata ( ExifProfile tags )
private ( ImageFrameMetadata FrameMetadata , TiffFrameMetadata TiffMetadata ) CreateFrameMetadata ( ExifProfile tags )
{
ImageFrameMetadata imageFrameMetaData = new ( ) ;
if ( ! this . skipMetadata )
@ -267,9 +282,10 @@ internal class TiffDecoderCore : ImageDecoderCore
imageFrameMetaData . ExifProfile = tags ;
}
TiffFrameMetadata . Parse ( imageFrameMetaData . GetTiffMetadata ( ) , tags ) ;
TiffFrameMetadata tiffMetadata = TiffFrameMetadata . Parse ( tags ) ;
imageFrameMetaData . SetFormatMetadata ( TiffFormat . Instance , tiffMetadata ) ;
return imageFrameMetaData ;
return ( imageFrameMetaData , tiffMetadata ) ;
}
/// <summary>
@ -278,8 +294,10 @@ internal class TiffDecoderCore : ImageDecoderCore
/// <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="width">The width in px units of the frame data.</param>
/// <param name="height">The height in px units of the frame data.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeImageWithStrips < TPixel > ( ExifProfile tags , ImageFrame < TPixel > frame , CancellationToken cancellationToken )
private void DecodeImageWithStrips < TPixel > ( ExifProfile tags , ImageFrame < TPixel > frame , int width , int height , CancellationToken cancellationToken )
where TPixel : unmanaged , IPixel < TPixel >
{
int rowsPerStrip ;
@ -302,6 +320,8 @@ internal class TiffDecoderCore : ImageDecoderCore
{
this . DecodeStripsPlanar (
frame ,
width ,
height ,
rowsPerStrip ,
stripOffsets ,
stripByteCounts ,
@ -311,6 +331,8 @@ internal class TiffDecoderCore : ImageDecoderCore
{
this . DecodeStripsChunky (
frame ,
width ,
height ,
rowsPerStrip ,
stripOffsets ,
stripByteCounts ,
@ -324,13 +346,13 @@ internal class TiffDecoderCore : ImageDecoderCore
/// <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="width">The width in px units of the frame data.</param>
/// <param name="height">The height in px units of the frame data.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeImageWithTiles < TPixel > ( ExifProfile tags , ImageFrame < TPixel > frame , CancellationToken cancellationToken )
private void DecodeImageWithTiles < TPixel > ( ExifProfile tags , ImageFrame < TPixel > frame , int width , int height , CancellationToken cancellationToken )
where TPixel : unmanaged , IPixel < TPixel >
{
Buffer2D < TPixel > pixels = frame . PixelBuffer ;
int width = pixels . Width ;
int height = pixels . Height ;
if ( ! tags . TryGetValue ( ExifTag . TileWidth , out IExifValue < Number > valueWidth ) )
{
@ -384,11 +406,20 @@ internal class TiffDecoderCore : ImageDecoderCore
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame to decode data into.</param>
/// <param name="width">The width in px units of the frame data.</param>
/// <param name="height">The height in px units of the frame data.</param>
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeStripsPlanar < TPixel > ( ImageFrame < TPixel > frame , int rowsPerStrip , Span < ulong > stripOffsets , Span < ulong > stripByteCounts , CancellationToken cancellationToken )
private void DecodeStripsPlanar < TPixel > (
ImageFrame < TPixel > frame ,
int width ,
int height ,
int rowsPerStrip ,
Span < ulong > stripOffsets ,
Span < ulong > stripByteCounts ,
CancellationToken cancellationToken )
where TPixel : unmanaged , IPixel < TPixel >
{
int stripsPerPixel = this . BitsPerSample . Channels ;
@ -403,18 +434,18 @@ internal class TiffDecoderCore : ImageDecoderCore
{
for ( int stripIndex = 0 ; stripIndex < stripBuffers . Length ; stripIndex + + )
{
int uncompressedStripSize = this . CalculateStripBufferSize ( frame . W idth, rowsPerStrip , stripIndex ) ;
int uncompressedStripSize = this . CalculateStripBufferSize ( w idth, rowsPerStrip , stripIndex ) ;
stripBuffers [ stripIndex ] = this . memoryAllocator . Allocate < byte > ( uncompressedStripSize ) ;
}
using TiffBaseDecompressor decompressor = this . CreateDecompressor < TPixel > ( frame . W idth, bitsPerPixel ) ;
using TiffBaseDecompressor decompressor = this . CreateDecompressor < TPixel > ( w idth, bitsPerPixel ) ;
TiffBasePlanarColorDecoder < TPixel > colorDecoder = this . CreatePlanarColorDecoder < TPixel > ( ) ;
for ( int i = 0 ; i < stripsPerPlane ; i + + )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
int stripHeight = i < stripsPerPlane - 1 | | frame . H eight % rowsPerStrip = = 0 ? rowsPerStrip : frame . H eight % rowsPerStrip ;
int stripHeight = i < stripsPerPlane - 1 | | h eight % rowsPerStrip = = 0 ? rowsPerStrip : h eight % rowsPerStrip ;
int stripIndex = i ;
for ( int planeIndex = 0 ; planeIndex < stripsPerPixel ; planeIndex + + )
@ -430,7 +461,7 @@ internal class TiffDecoderCore : ImageDecoderCore
stripIndex + = stripsPerPlane ;
}
colorDecoder . Decode ( stripBuffers , pixels , 0 , rowsPerStrip * i , frame . W idth, stripHeight ) ;
colorDecoder . Decode ( stripBuffers , pixels , 0 , rowsPerStrip * i , w idth, stripHeight ) ;
}
}
finally
@ -447,39 +478,48 @@ internal class TiffDecoderCore : ImageDecoderCore
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame to decode data into.</param>
/// <param name="width">The width in px units of the frame data.</param>
/// <param name="height">The height in px units of the frame data.</param>
/// <param name="rowsPerStrip">The rows per strip.</param>
/// <param name="stripOffsets">The strip offsets.</param>
/// <param name="stripByteCounts">The strip byte counts.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeStripsChunky < TPixel > ( ImageFrame < TPixel > frame , int rowsPerStrip , Span < ulong > stripOffsets , Span < ulong > stripByteCounts , CancellationToken cancellationToken )
private void DecodeStripsChunky < TPixel > (
ImageFrame < TPixel > frame ,
int width ,
int height ,
int rowsPerStrip ,
Span < ulong > stripOffsets ,
Span < ulong > stripByteCounts ,
CancellationToken cancellationToken )
where TPixel : unmanaged , IPixel < TPixel >
{
// If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip.
if ( rowsPerStrip = = TiffConstants . RowsPerStripInfinity )
{
rowsPerStrip = frame . Height ;
rowsPerStrip = h eight;
}
int uncompressedStripSize = this . CalculateStripBufferSize ( frame . W idth, rowsPerStrip ) ;
int uncompressedStripSize = this . CalculateStripBufferSize ( w idth, rowsPerStrip ) ;
int bitsPerPixel = this . BitsPerPixel ;
using IMemoryOwner < byte > stripBuffer = this . memoryAllocator . Allocate < byte > ( uncompressedStripSize , AllocationOptions . Clean ) ;
Span < byte > stripBufferSpan = stripBuffer . GetSpan ( ) ;
Buffer2D < TPixel > pixels = frame . PixelBuffer ;
using TiffBaseDecompressor decompressor = this . CreateDecompressor < TPixel > ( frame . W idth, bitsPerPixel ) ;
using TiffBaseDecompressor decompressor = this . CreateDecompressor < TPixel > ( w idth, bitsPerPixel ) ;
TiffBaseColorDecoder < TPixel > colorDecoder = this . CreateChunkyColorDecoder < TPixel > ( ) ;
for ( int stripIndex = 0 ; stripIndex < stripOffsets . Length ; stripIndex + + )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
int stripHeight = stripIndex < stripOffsets . Length - 1 | | frame . H eight % rowsPerStrip = = 0
int stripHeight = stripIndex < stripOffsets . Length - 1 | | h eight % rowsPerStrip = = 0
? rowsPerStrip
: frame . H eight % rowsPerStrip ;
: h eight % rowsPerStrip ;
int top = rowsPerStrip * stripIndex ;
if ( top + stripHeight > frame . H eight)
if ( top + stripHeight > h eight)
{
// Make sure we ignore any strips that are not needed for the image (if too many are present).
break ;
@ -493,7 +533,7 @@ internal class TiffDecoderCore : ImageDecoderCore
stripBufferSpan ,
cancellationToken ) ;
colorDecoder . Decode ( stripBufferSpan , pixels , 0 , top , frame . W idth, stripHeight ) ;
colorDecoder . Decode ( stripBufferSpan , pixels , 0 , top , w idth, stripHeight ) ;
}
}
@ -790,38 +830,6 @@ internal class TiffDecoderCore : ImageDecoderCore
return bytesPerRow * height ;
}
/// <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 )
{
if ( ! exifProfile . TryGetValue ( ExifTag . ImageWidth , out IExifValue < Number > width ) )
{
TiffThrowHelper . ThrowInvalidImageContentException ( "The TIFF image frame is missing the ImageWidth" ) ;
}
DebugGuard . MustBeLessThanOrEqualTo ( ( ulong ) width . Value , ( ulong ) int . MaxValue , nameof ( ExifTag . ImageWidth ) ) ;
return ( int ) width . Value ;
}
/// <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 )
{
if ( ! exifProfile . TryGetValue ( ExifTag . ImageLength , out IExifValue < Number > height ) )
{
TiffThrowHelper . ThrowImageFormatException ( "The TIFF image frame is missing the ImageLength" ) ;
}
return ( int ) height . Value ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int RoundUpToMultipleOfEight ( int value ) = > ( int ) ( ( ( uint ) value + 7 ) / 8 ) ;
}