Skip to content

Commit eb5c05e

Browse files
Allow decoding Tiff of different frame size.
1 parent 8da27c9 commit eb5c05e

17 files changed

+102
-67
lines changed

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ private void EncodeAdditionalFrames<TPixel>(
209209
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
210210

211211
// This frame is reused to store de-duplicated pixel buffers.
212-
using ImageFrame<TPixel> encodingFrame = new(previousFrame.Configuration, previousFrame.Size());
212+
using ImageFrame<TPixel> encodingFrame = new(previousFrame.Configuration, previousFrame.Size);
213213

214214
for (int i = 1; i < image.Frames.Count; i++)
215215
{

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
231231
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
232232

233233
// This frame is reused to store de-duplicated pixel buffers.
234-
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
234+
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size);
235235

236236
for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
237237
{

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,18 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
167167
this.byteOrder = reader.ByteOrder;
168168
this.isBigTiff = reader.IsBigTiff;
169169

170+
Size? size = null;
170171
uint frameCount = 0;
171172
foreach (ExifProfile ifd in directories)
172173
{
173174
cancellationToken.ThrowIfCancellationRequested();
174-
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
175+
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, size, cancellationToken);
176+
177+
if (!size.HasValue)
178+
{
179+
size = frame.Size;
180+
}
181+
175182
frames.Add(frame);
176183
framesMetadata.Add(frame.Metadata);
177184

@@ -181,19 +188,8 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
181188
}
182189
}
183190

191+
this.Dimensions = frames[0].Size;
184192
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff);
185-
186-
// TODO: Tiff frames can have different sizes.
187-
ImageFrame<TPixel> root = frames[0];
188-
this.Dimensions = root.Size();
189-
foreach (ImageFrame<TPixel> frame in frames)
190-
{
191-
if (frame.Size() != root.Size())
192-
{
193-
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported");
194-
}
195-
}
196-
197193
return new Image<TPixel>(this.configuration, metadata, frames);
198194
}
199195
catch
@@ -235,25 +231,40 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok
235231
/// </summary>
236232
/// <typeparam name="TPixel">The pixel format.</typeparam>
237233
/// <param name="tags">The IFD tags.</param>
234+
/// <param name="size">The previously determined root frame size if decoded.</param>
238235
/// <param name="cancellationToken">The token to monitor cancellation.</param>
239236
/// <returns>The tiff frame.</returns>
240-
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, CancellationToken cancellationToken)
237+
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, Size? size, CancellationToken cancellationToken)
241238
where TPixel : unmanaged, IPixel<TPixel>
242239
{
243240
ImageFrameMetadata imageFrameMetaData = this.CreateFrameMetadata(tags);
244241
bool isTiled = this.VerifyAndParse(tags, imageFrameMetaData.GetTiffMetadata());
245242

246243
int width = GetImageWidth(tags);
247244
int height = GetImageHeight(tags);
248-
ImageFrame<TPixel> frame = new(this.configuration, width, height, imageFrameMetaData);
245+
246+
// If size has a value and the width/height off the tiff is smaller we much capture the delta.
247+
if (size.HasValue)
248+
{
249+
if (size.Value.Width < width || size.Value.Height < height)
250+
{
251+
TiffThrowHelper.ThrowNotSupported("Images with frames of size greater than the root frame are not supported.");
252+
}
253+
}
254+
else
255+
{
256+
size = new Size(width, height);
257+
}
258+
259+
ImageFrame<TPixel> frame = new(this.configuration, size.Value.Width, size.Value.Height, imageFrameMetaData);
249260

250261
if (isTiled)
251262
{
252-
this.DecodeImageWithTiles(tags, frame, cancellationToken);
263+
this.DecodeImageWithTiles(tags, frame, width, height, cancellationToken);
253264
}
254265
else
255266
{
256-
this.DecodeImageWithStrips(tags, frame, cancellationToken);
267+
this.DecodeImageWithStrips(tags, frame, width, height, cancellationToken);
257268
}
258269

259270
return frame;
@@ -278,8 +289,10 @@ private ImageFrameMetadata CreateFrameMetadata(ExifProfile tags)
278289
/// <typeparam name="TPixel">The pixel format.</typeparam>
279290
/// <param name="tags">The IFD tags.</param>
280291
/// <param name="frame">The image frame to decode into.</param>
292+
/// <param name="width">The width in px units of the frame data.</param>
293+
/// <param name="height">The height in px units of the frame data.</param>
281294
/// <param name="cancellationToken">The token to monitor cancellation.</param>
282-
private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, CancellationToken cancellationToken)
295+
private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, int width, int height, CancellationToken cancellationToken)
283296
where TPixel : unmanaged, IPixel<TPixel>
284297
{
285298
int rowsPerStrip;
@@ -302,6 +315,8 @@ private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel>
302315
{
303316
this.DecodeStripsPlanar(
304317
frame,
318+
width,
319+
height,
305320
rowsPerStrip,
306321
stripOffsets,
307322
stripByteCounts,
@@ -311,6 +326,8 @@ private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel>
311326
{
312327
this.DecodeStripsChunky(
313328
frame,
329+
width,
330+
height,
314331
rowsPerStrip,
315332
stripOffsets,
316333
stripByteCounts,
@@ -324,13 +341,13 @@ private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel>
324341
/// <typeparam name="TPixel">The pixel format.</typeparam>
325342
/// <param name="tags">The IFD tags.</param>
326343
/// <param name="frame">The image frame to decode into.</param>
344+
/// <param name="width">The width in px units of the frame data.</param>
345+
/// <param name="height">The height in px units of the frame data.</param>
327346
/// <param name="cancellationToken">The token to monitor cancellation.</param>
328-
private void DecodeImageWithTiles<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, CancellationToken cancellationToken)
347+
private void DecodeImageWithTiles<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, int width, int height, CancellationToken cancellationToken)
329348
where TPixel : unmanaged, IPixel<TPixel>
330349
{
331350
Buffer2D<TPixel> pixels = frame.PixelBuffer;
332-
int width = pixels.Width;
333-
int height = pixels.Height;
334351

335352
if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue<Number> valueWidth))
336353
{
@@ -384,11 +401,20 @@ private void DecodeImageWithTiles<TPixel>(ExifProfile tags, ImageFrame<TPixel> f
384401
/// </summary>
385402
/// <typeparam name="TPixel">The pixel format.</typeparam>
386403
/// <param name="frame">The image frame to decode data into.</param>
404+
/// <param name="width">The width in px units of the frame data.</param>
405+
/// <param name="height">The height in px units of the frame data.</param>
387406
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
388407
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
389408
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
390409
/// <param name="cancellationToken">The token to monitor cancellation.</param>
391-
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Span<ulong> stripOffsets, Span<ulong> stripByteCounts, CancellationToken cancellationToken)
410+
private void DecodeStripsPlanar<TPixel>(
411+
ImageFrame<TPixel> frame,
412+
int width,
413+
int height,
414+
int rowsPerStrip,
415+
Span<ulong> stripOffsets,
416+
Span<ulong> stripByteCounts,
417+
CancellationToken cancellationToken)
392418
where TPixel : unmanaged, IPixel<TPixel>
393419
{
394420
int stripsPerPixel = this.BitsPerSample.Channels;
@@ -403,18 +429,18 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
403429
{
404430
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
405431
{
406-
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
432+
int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex);
407433
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
408434
}
409435

410-
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
436+
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel);
411437
TiffBasePlanarColorDecoder<TPixel> colorDecoder = this.CreatePlanarColorDecoder<TPixel>();
412438

413439
for (int i = 0; i < stripsPerPlane; i++)
414440
{
415441
cancellationToken.ThrowIfCancellationRequested();
416442

417-
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
443+
int stripHeight = i < stripsPerPlane - 1 || height % rowsPerStrip == 0 ? rowsPerStrip : height % rowsPerStrip;
418444

419445
int stripIndex = i;
420446
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
@@ -430,7 +456,7 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
430456
stripIndex += stripsPerPlane;
431457
}
432458

433-
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
459+
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, width, stripHeight);
434460
}
435461
}
436462
finally
@@ -447,39 +473,48 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
447473
/// </summary>
448474
/// <typeparam name="TPixel">The pixel format.</typeparam>
449475
/// <param name="frame">The image frame to decode data into.</param>
476+
/// <param name="width">The width in px units of the frame data.</param>
477+
/// <param name="height">The height in px units of the frame data.</param>
450478
/// <param name="rowsPerStrip">The rows per strip.</param>
451479
/// <param name="stripOffsets">The strip offsets.</param>
452480
/// <param name="stripByteCounts">The strip byte counts.</param>
453481
/// <param name="cancellationToken">The token to monitor cancellation.</param>
454-
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Span<ulong> stripOffsets, Span<ulong> stripByteCounts, CancellationToken cancellationToken)
482+
private void DecodeStripsChunky<TPixel>(
483+
ImageFrame<TPixel> frame,
484+
int width,
485+
int height,
486+
int rowsPerStrip,
487+
Span<ulong> stripOffsets,
488+
Span<ulong> stripByteCounts,
489+
CancellationToken cancellationToken)
455490
where TPixel : unmanaged, IPixel<TPixel>
456491
{
457492
// If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip.
458493
if (rowsPerStrip == TiffConstants.RowsPerStripInfinity)
459494
{
460-
rowsPerStrip = frame.Height;
495+
rowsPerStrip = height;
461496
}
462497

463-
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
498+
int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
464499
int bitsPerPixel = this.BitsPerPixel;
465500

466501
using IMemoryOwner<byte> stripBuffer = this.memoryAllocator.Allocate<byte>(uncompressedStripSize, AllocationOptions.Clean);
467502
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
468503
Buffer2D<TPixel> pixels = frame.PixelBuffer;
469504

470-
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
505+
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel);
471506
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
472507

473508
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
474509
{
475510
cancellationToken.ThrowIfCancellationRequested();
476511

477-
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0
512+
int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
478513
? rowsPerStrip
479-
: frame.Height % rowsPerStrip;
514+
: height % rowsPerStrip;
480515

481516
int top = rowsPerStrip * stripIndex;
482-
if (top + stripHeight > frame.Height)
517+
if (top + stripHeight > height)
483518
{
484519
// Make sure we ignore any strips that are not needed for the image (if too many are present).
485520
break;
@@ -493,7 +528,7 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
493528
stripBufferSpan,
494529
cancellationToken);
495530

496-
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
531+
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight);
497532
}
498533
}
499534

src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
160160

161161
// Encode additional frames
162162
// This frame is reused to store de-duplicated pixel buffers.
163-
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
163+
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size);
164164

165165
for (int i = 1; i < image.Frames.Count; i++)
166166
{
@@ -235,7 +235,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
235235

236236
// Encode additional frames
237237
// This frame is reused to store de-duplicated pixel buffers.
238-
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
238+
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size);
239239

240240
for (int i = 1; i < image.Frames.Count; i++)
241241
{

src/ImageSharp/Image.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ internal Image(
7777
/// <summary>
7878
/// Gets the size of the image in px units.
7979
/// </summary>
80-
public Size Size { get; internal set; }
80+
public Size Size { get; private set; }
8181

8282
/// <summary>
8383
/// Gets the bounds of the image.

src/ImageSharp/ImageFrame.cs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,19 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable
2525
protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata)
2626
{
2727
this.Configuration = configuration;
28-
this.Width = width;
29-
this.Height = height;
28+
this.Size = new(width, height);
3029
this.Metadata = metadata;
3130
}
3231

3332
/// <summary>
34-
/// Gets the width.
33+
/// Gets the frame width in px units.
3534
/// </summary>
36-
public int Width { get; private set; }
35+
public int Width => this.Size.Width;
3736

3837
/// <summary>
39-
/// Gets the height.
38+
/// Gets the frame height in px units.
4039
/// </summary>
41-
public int Height { get; private set; }
40+
public int Height => this.Size.Height;
4241

4342
/// <summary>
4443
/// Gets the metadata of the frame.
@@ -51,8 +50,7 @@ protected ImageFrame(Configuration configuration, int width, int height, ImageFr
5150
/// <summary>
5251
/// Gets the size of the frame.
5352
/// </summary>
54-
/// <returns>The <see cref="Size"/></returns>
55-
public Size Size() => new(this.Width, this.Height);
53+
public Size Size { get; private set; }
5654

5755
/// <summary>
5856
/// Gets the bounds of the frame.
@@ -80,9 +78,5 @@ internal abstract void CopyPixelsTo<TDestinationPixel>(MemoryGroup<TDestinationP
8078
/// Updates the size of the image frame.
8179
/// </summary>
8280
/// <param name="size">The size.</param>
83-
internal void UpdateSize(Size size)
84-
{
85-
this.Width = size.Width;
86-
this.Height = size.Height;
87-
}
81+
protected void UpdateSize(Size size) => this.Size = size;
8882
}

src/ImageSharp/ImageFrameCollection{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ private ImageFrame<TPixel> CopyNonCompatibleFrame(ImageFrame source)
414414
{
415415
ImageFrame<TPixel> result = new(
416416
this.parent.Configuration,
417-
source.Size(),
417+
source.Size,
418418
source.Metadata.DeepClone());
419419
source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup);
420420
return result;

src/ImageSharp/ImageFrame{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ public bool DangerousTryGetSinglePixelMemory(out Memory<TPixel> memory)
322322
/// <exception cref="ArgumentException">ImageFrame{TPixel}.CopyTo(): target must be of the same size!</exception>
323323
internal void CopyTo(Buffer2D<TPixel> target)
324324
{
325-
if (this.Size() != target.Size())
325+
if (this.Size != target.Size())
326326
{
327327
throw new ArgumentException("ImageFrame<TPixel>.CopyTo(): target must be of the same size!", nameof(target));
328328
}

src/ImageSharp/Image{TPixel}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,9 @@ private static Size ValidateFramesAndGetSize(IEnumerable<ImageFrame<TPixel>> fra
419419

420420
ImageFrame<TPixel>? rootFrame = frames.FirstOrDefault() ?? throw new ArgumentException("Must not be empty.", nameof(frames));
421421

422-
Size rootSize = rootFrame.Size();
422+
Size rootSize = rootFrame.Size;
423423

424-
if (frames.Any(f => f.Size() != rootSize))
424+
if (frames.Any(f => f.Size != rootSize))
425425
{
426426
throw new ArgumentException("The provided frames must be of the same size.", nameof(frames));
427427
}

src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
9696
}
9797

9898
// Create a 0-filled buffer to use to store the result of the component convolutions
99-
using Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean);
99+
using Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size, AllocationOptions.Clean);
100100

101101
// Perform the 1D convolutions on all the kernel components and accumulate the results
102102
this.OnFrameApplyCore(source, sourceRectangle, this.Configuration, processingBuffer);
@@ -134,7 +134,7 @@ private void OnFrameApplyCore(
134134
Buffer2D<Vector4> processingBuffer)
135135
{
136136
// Allocate the buffer with the intermediate convolution results
137-
using Buffer2D<ComplexVector4> firstPassBuffer = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size());
137+
using Buffer2D<ComplexVector4> firstPassBuffer = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size);
138138

139139
// Unlike in the standard 2 pass convolution processor, we use a rectangle of 1x the interest width
140140
// to speedup the actual convolution, by applying bulk pixel conversion and clamping calculation.

0 commit comments

Comments
 (0)