From 00331086fae80a0501da43220d9c7767637c9985 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 1 Dec 2020 12:33:51 +0100 Subject: [PATCH] Add support for undoing horizontal prediction: Works with deflate, still some issue with lzw --- .../Tiff/Constants/TiffPlanarConfiguration.cs | 10 +++++++ src/ImageSharp/Formats/Tiff/README.md | 9 ++++++ .../Formats/Tiff/TiffDecoderCore.cs | 30 +++++++++++++++++++ .../Formats/Tiff/TiffDecoderHelpers.cs | 6 ++-- tests/ImageSharp.Tests/TestImages.cs | 10 ++++--- .../{rgb_lzw.tiff => rgb_lzw_predictor.tiff} | 0 6 files changed, 58 insertions(+), 7 deletions(-) rename tests/Images/Input/Tiff/{rgb_lzw.tiff => rgb_lzw_predictor.tiff} (100%) diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs index 6249a935ed..ea526ede56 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs @@ -10,11 +10,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants { /// /// Chunky format. + /// The component values for each pixel are stored contiguously. + /// The order of the components within the pixel is specified by + /// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB. /// Chunky = 1, /// /// Planar format. + /// The components are stored in separate “component planes.” The + /// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional + /// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns + /// for row 0 are stored first, followed by the columns of row 1, and so on.) + /// PhotometricInterpretation describes the type of data stored in each component + /// plane. For example, RGB data is stored with the Red components in one component + /// plane, the Green in another, and the Blue in another. /// Planar = 2 } diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index a87813a23d..bb5e93c725 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -25,6 +25,15 @@ ## Implementation Status +### Know issue which need to be fixed + +Decoder: +- Decoding HUffman RLE for `Calliphora_huffman_rle.tiff` has 4 pixels difference to the reference decoder. Al those are at the very edge of the image (reason unknown so far). +- Decoding compressed images with HorizontalPrediction: Works for deflate, but not for lzw. + +Encoder: +- Encoding image with a palette have a difference of 0.0043% to the ReferenceDecoder (ImageMagick) + ### Deviations from the TIFF spec (to be fixed) - Decoder diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 4c9b0b48df..d29cf74387 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -240,6 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff var coreMetadata = new ImageFrameMetadata(); frameMetaData = coreMetadata.GetTiffMetadata(); frameMetaData.Tags = tags; + TiffFrameMetadata tiffFormatMetaData = coreMetadata.GetFormatMetadata(TiffFormat.Instance); this.VerifyAndParseOptions(frameMetaData); @@ -260,9 +261,38 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width); } + if (tiffFormatMetaData.Predictor == TiffPredictor.Horizontal) + { + this.UndoHorizontalPredictor(width, height, frame); + } + return frame; } + private void UndoHorizontalPredictor(int width, int height, ImageFrame frame) + where TPixel : unmanaged, IPixel + { + using System.Buffers.IMemoryOwner rowRgbBuffer = this.memoryAllocator.Allocate(width); + System.Span rowRgb = rowRgbBuffer.GetSpan(); + for (int y = 0; y < height; y++) + { + System.Span pixelRow = frame.GetPixelRowSpan(y); + PixelOperations.Instance.ToRgb24(this.configuration, pixelRow, rowRgb); + byte r = rowRgb[0].R; + byte g = rowRgb[0].G; + byte b = rowRgb[0].B; + for (int x = 1; x < width; x++) + { + ref TPixel pixel = ref pixelRow[x]; + r += rowRgb[x].R; + g += rowRgb[x].G; + b += rowRgb[x].B; + var rgb = new Rgb24(r, g, b); + pixel.FromRgb24(rgb); + } + } + } + /// /// Calculates the size (in bytes) for a pixel buffer using the determined color format. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs index d127fd8703..5ae58c6a61 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs @@ -106,9 +106,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowNotSupported("The Tile images is not supported."); } - if (entries.Predictor != TiffPredictor.None) + if (entries.Predictor == TiffPredictor.FloatingPoint) { - TiffThrowHelper.ThrowNotSupported("At the moment we support only None Predictor images."); + TiffThrowHelper.ThrowNotSupported("ImageSharp does not support FloatingPoint Predictor images."); } if (entries.SampleFormat != null) @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (format != TiffSampleFormat.UnsignedInteger) { - TiffThrowHelper.ThrowNotSupported("At the moment support only UnsignedInteger SampleFormat."); + TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger SampleFormat."); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6bce48eb28..9f7516fe2a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -529,7 +529,7 @@ namespace SixLabors.ImageSharp.Tests public const string RgbDeflate_Predictor = "Tiff/rgb_deflate.tiff"; public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; public const string RgbJpeg = "Tiff/rgb_jpeg.tiff"; - public const string RgbLzw_Predictor = "Tiff/rgb_lzw.tiff"; + public const string RgbLzw_Predictor = "Tiff/rgb_lzw_predictor.tiff"; public const string RgbLzw_NoPredictor_Multistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; public const string RgbLzw_NoPredictor_Multistrip_Motorola = "Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff"; public const string RgbLzw_NoPredictor_Singlestrip_Motorola = "Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff"; @@ -553,10 +553,12 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { - Calliphora_PaletteUncompressed, /*Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, */ Calliphora_RgbPackbits, + Calliphora_PaletteUncompressed, Calliphora_RgbPackbits, + Calliphora_GrayscaleDeflate_Predictor, Calliphora_RgbDeflate_Predictor, RgbDeflate_Predictor, + Calliphora_RgbLzw_Predictor, RgbLzw_Predictor, // TODO: Undoing the horizontal prediction seems to fail for lzw. Do we need to do something different for lzw? Calliphora_BiColor, Calliphora_RgbUncompressed, Calliphora_HuffmanCompressed, Calliphora_Fax3Compressed, CcittFax3AllTermCodes, CcittFax3AllMakeupCodes, HuffmanRleAllTermCodes, HuffmanRleAllMakeupCodes, GrayscaleDeflateMultistrip, Calliphora_GrayscaleDeflate, Calliphora_GrayscaleUncompressed, - GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, /*RgbDeflate_Predictor,*/ RgbDeflateMultistrip, /*RgbJpeg,*/ /*RgbLzw_Predictor, RgbLzwMultistrip_Predictor,*/ + GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, RgbDeflateMultistrip, /*RgbJpeg,*/ /* RgbLzwMultistrip_Predictor,*/ RgbLzw_NoPredictor_Multistrip, RgbLzw_NoPredictor_Multistrip_Motorola, RgbLzw_NoPredictor_Singlestrip_Motorola, RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, /* MultiframeLzw_Predictor, MultiFrameDifferentVariants, SampleMetadata,*/ SmallRgbDeflate, SmallRgbLzw, }; @@ -565,7 +567,7 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] Metadata = { SampleMetadata }; - public static readonly string[] NotSupported = { Calliphora_GrayscaleDeflate_Predictor, Calliphora_RgbJpeg, Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzw_Predictor, RgbDeflate_Predictor, RgbLzw_Predictor, RgbLzwMultistrip_Predictor, RgbJpeg, RgbUncompressedTiled, MultiframeLzw_Predictor, MultiframeDifferentSize, MultiframeDifferentVariants }; + public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbLzwMultistrip_Predictor, RgbJpeg, RgbUncompressedTiled, MultiframeLzw_Predictor, MultiframeDifferentSize, MultiframeDifferentVariants }; } } } diff --git a/tests/Images/Input/Tiff/rgb_lzw.tiff b/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff similarity index 100% rename from tests/Images/Input/Tiff/rgb_lzw.tiff rename to tests/Images/Input/Tiff/rgb_lzw_predictor.tiff