diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
index 27c311009c..3e874b7d23 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
@@ -22,6 +22,8 @@ internal sealed class DeflateTiffCompression : TiffBaseDecompressor
private readonly TiffColorType colorType;
+ private readonly bool isTiled;
+
///
/// Initializes a new instance of the class.
///
@@ -31,11 +33,13 @@ internal sealed class DeflateTiffCompression : TiffBaseDecompressor
/// The color type of the pixel data.
/// The tiff predictor used.
/// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian)
+ /// Flag indicates, if the image is a tiled image.
+ public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian, bool isTiled)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{
this.colorType = colorType;
this.isBigEndian = isBigEndian;
+ this.isTiled = isTiled;
}
///
@@ -68,7 +72,8 @@ internal sealed class DeflateTiffCompression : TiffBaseDecompressor
}
}
- if (this.Predictor == TiffPredictor.Horizontal)
+ // When the image is tiled, undoing the horizontal predictor will be done for each tile row in the DecodeTilesChunky() method.
+ if (this.Predictor == TiffPredictor.Horizontal && !this.isTiled)
{
HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian);
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
index 30a9335286..21306e43ff 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
@@ -62,6 +62,22 @@ internal static class HorizontalPredictor
}
}
+ public static void UndoRow(Span pixelBytes, int width, int y, TiffColorType colorType)
+ {
+ // TODO: Implement missing colortypes, see above.
+ switch (colorType)
+ {
+ case TiffColorType.Rgb888:
+ case TiffColorType.CieLab:
+ UndoRgb24BitRow(pixelBytes, width, y);
+ break;
+ case TiffColorType.Rgba8888:
+ case TiffColorType.Cmyk:
+ UndoRgba32BitRow(pixelBytes, width, y);
+ break;
+ }
+ }
+
public static void ApplyHorizontalPrediction(Span rows, int width, int bitsPerPixel)
{
if (bitsPerPixel == 8)
@@ -257,27 +273,56 @@ internal static class HorizontalPredictor
}
}
+ private static void UndoRgb24BitRow(Span pixelBytes, int width, int y)
+ {
+ int rowBytesCount = width * 3;
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width];
+ ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb);
+ byte r = rowRgbBase.R;
+ byte g = rowRgbBase.G;
+ byte b = rowRgbBase.B;
+
+ for (int x = 1; x < rowRgb.Length; x++)
+ {
+ ref Rgb24 pixel = ref rowRgb[x];
+ r += pixel.R;
+ g += pixel.G;
+ b += pixel.B;
+ pixel = new Rgb24(r, g, b);
+ }
+ }
+
private static void UndoRgb24Bit(Span pixelBytes, int width)
{
int rowBytesCount = width * 3;
int height = pixelBytes.Length / rowBytesCount;
for (int y = 0; y < height; y++)
{
- Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
- Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width];
- ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb);
- byte r = rowRgbBase.R;
- byte g = rowRgbBase.G;
- byte b = rowRgbBase.B;
+ UndoRgb24BitRow(pixelBytes, width, y);
+ }
+ }
- for (int x = 1; x < rowRgb.Length; x++)
- {
- ref Rgb24 pixel = ref rowRgb[x];
- r += pixel.R;
- g += pixel.G;
- b += pixel.B;
- pixel = new Rgb24(r, g, b);
- }
+ private static void UndoRgba32BitRow(Span pixelBytes, int width, int y)
+ {
+ int rowBytesCount = width * 4;
+
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width];
+ ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb);
+ byte r = rowRgbBase.R;
+ byte g = rowRgbBase.G;
+ byte b = rowRgbBase.B;
+ byte a = rowRgbBase.A;
+
+ for (int x = 1; x < rowRgb.Length; x++)
+ {
+ ref Rgba32 pixel = ref rowRgb[x];
+ r += pixel.R;
+ g += pixel.G;
+ b += pixel.B;
+ a += pixel.A;
+ pixel = new Rgba32(r, g, b, a);
}
}
@@ -287,23 +332,7 @@ internal static class HorizontalPredictor
int height = pixelBytes.Length / rowBytesCount;
for (int y = 0; y < height; y++)
{
- Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
- Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width];
- ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb);
- byte r = rowRgbBase.R;
- byte g = rowRgbBase.G;
- byte b = rowRgbBase.B;
- byte a = rowRgbBase.A;
-
- for (int x = 1; x < rowRgb.Length; x++)
- {
- ref Rgba32 pixel = ref rowRgb[x];
- r += pixel.R;
- g += pixel.G;
- b += pixel.B;
- a += pixel.A;
- pixel = new Rgba32(r, g, b, a);
- }
+ UndoRgba32BitRow(pixelBytes, width, y);
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
index 720e376b9d..c05d14dc6c 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
@@ -24,7 +24,8 @@ internal static class TiffDecompressorsFactory
byte[] jpegTables,
uint oldJpegStartOfImageMarker,
TiffFillOrder fillOrder,
- ByteOrder byteOrder)
+ ByteOrder byteOrder,
+ bool isTiled = false)
{
switch (method)
{
@@ -40,7 +41,7 @@ internal static class TiffDecompressorsFactory
case TiffDecoderCompressionType.Deflate:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
- return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian);
+ return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian, isTiled);
case TiffDecoderCompressionType.Lzw:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index d699a7b631..43d7944eab 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -683,7 +683,8 @@ internal class TiffDecoderCore : ImageDecoderCore
Span tileBufferSpan = tileBuffer.GetSpan();
Span uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan();
- using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel);
+ bool isTiled = true;
+ using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel, isTiled);
TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder();
int tileIndex = 0;
@@ -712,6 +713,13 @@ internal class TiffDecoderCore : ImageDecoderCore
{
Span uncompressedPixelRow = uncompressedPixelBufferSpan.Slice(uncompressedPixelBufferOffset, bytesToCopy);
tileBufferSpan.Slice(tileBufferOffset, bytesToCopy).CopyTo(uncompressedPixelRow);
+
+ // Undo the horziontal predictor for each tile row.
+ if (this.Predictor == TiffPredictor.Horizontal)
+ {
+ HorizontalPredictor.UndoRow(uncompressedPixelRow, tileLength, 0, this.ColorType);
+ }
+
tileBufferOffset += bytesPerTileRow;
uncompressedPixelBufferOffset += bytesPerRow;
}
@@ -750,7 +758,7 @@ internal class TiffDecoderCore : ImageDecoderCore
this.YcbcrSubSampling,
this.byteOrder);
- private TiffBaseDecompressor CreateDecompressor(int frameWidth, int bitsPerPixel)
+ private TiffBaseDecompressor CreateDecompressor(int frameWidth, int bitsPerPixel, bool isTiled = false)
where TPixel : unmanaged, IPixel =>
TiffDecompressorsFactory.Create(
this.Options,
@@ -765,7 +773,8 @@ internal class TiffDecoderCore : ImageDecoderCore
this.JpegTables,
this.OldJpegCompressionStartOfImageMarker.GetValueOrDefault(),
this.FillOrder,
- this.byteOrder);
+ this.byteOrder,
+ isTiled);
private IMemoryOwner ConvertNumbers(Array array, out Span span)
{
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
index 1b12adac23..b0ca4699e5 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
@@ -23,7 +23,7 @@ public class DeflateTiffCompressionTests
using BufferedReadStream stream = CreateCompressedStream(data);
byte[] buffer = new byte[data.Length];
- using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
+ using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default);