From f72be91d106d6e05c2e19dbddd0c0cde096d7032 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 2 Feb 2025 19:27:42 +0100 Subject: [PATCH] Undo horizontal prediction for each tile row in case of tiled tiff's --- .../Decompressors/DeflateTiffCompression.cs | 9 +- .../Tiff/Compression/HorizontalPredictor.cs | 91 ++++++++++++------- .../Compression/TiffDecompressorsFactory.cs | 5 +- .../Formats/Tiff/TiffDecoderCore.cs | 15 ++- .../DeflateTiffCompressionTests.cs | 2 +- 5 files changed, 83 insertions(+), 39 deletions(-) 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);