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