diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
index e2dbc6ca99..97bce2b2db 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
@@ -22,7 +22,7 @@ internal static class HorizontalPredictor
/// Buffer with decompressed pixel data.
/// The width of the image or strip.
/// The color type of the pixel data.
- /// if set to true decodes the pixel data as big endian, otherwise as little endian.
+ /// If set to true decodes the pixel data as big endian, otherwise as little endian.
public static void Undo(Span pixelBytes, int width, TiffColorType colorType, bool isBigEndian)
{
switch (colorType)
@@ -43,12 +43,21 @@ public static void Undo(Span pixelBytes, int width, TiffColorType colorTyp
case TiffColorType.Rgb888:
UndoRgb24Bit(pixelBytes, width);
break;
+ case TiffColorType.Rgba8888:
+ UndoRgba32Bit(pixelBytes, width);
+ break;
case TiffColorType.Rgb161616:
UndoRgb48Bit(pixelBytes, width, isBigEndian);
break;
+ case TiffColorType.Rgba16161616:
+ UndoRgba64Bit(pixelBytes, width, isBigEndian);
+ break;
case TiffColorType.Rgb323232:
UndoRgb96Bit(pixelBytes, width, isBigEndian);
break;
+ case TiffColorType.Rgba32323232:
+ UndoRgba128Bit(pixelBytes, width, isBigEndian);
+ break;
}
}
@@ -243,6 +252,33 @@ private static void UndoRgb24Bit(Span pixelBytes, int width)
}
}
+ private static void UndoRgba32Bit(Span pixelBytes, int width)
+ {
+ int rowBytesCount = width * 4;
+ int height = pixelBytes.Length / rowBytesCount;
+ for (int y = 0; y < height; y++)
+ {
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ Span rowRgb = MemoryMarshal.Cast(rowBytes).Slice(0, 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;
+ var rgb = new Rgba32(r, g, b, a);
+ pixel.FromRgba32(rgb);
+ }
+ }
+ }
+
private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 6;
@@ -319,6 +355,98 @@ private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEnd
}
}
+ private static void UndoRgba64Bit(Span pixelBytes, int width, bool isBigEndian)
+ {
+ int rowBytesCount = width * 8;
+ int height = pixelBytes.Length / rowBytesCount;
+ if (isBigEndian)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ int offset = 0;
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+ ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+ ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+ ushort a = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+
+ for (int x = 1; x < width; x++)
+ {
+ Span rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan);
+ r += deltaR;
+ BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r);
+ offset += 2;
+
+ rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan);
+ g += deltaG;
+ BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g);
+ offset += 2;
+
+ rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan);
+ b += deltaB;
+ BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b);
+ offset += 2;
+
+ rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaA = TiffUtils.ConvertToUShortBigEndian(rowSpan);
+ a += deltaA;
+ BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a);
+ offset += 2;
+ }
+ }
+ }
+ else
+ {
+ for (int y = 0; y < height; y++)
+ {
+ int offset = 0;
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+ ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+ ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+ ushort a = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
+ offset += 2;
+
+ for (int x = 1; x < width; x++)
+ {
+ Span rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
+ r += deltaR;
+ BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r);
+ offset += 2;
+
+ rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
+ g += deltaG;
+ BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g);
+ offset += 2;
+
+ rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
+ b += deltaB;
+ BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b);
+ offset += 2;
+
+ rowSpan = rowBytes.Slice(offset, 2);
+ ushort deltaA = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
+ a += deltaA;
+ BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a);
+ offset += 2;
+ }
+ }
+ }
+ }
+
private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 12;
@@ -394,5 +522,97 @@ private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEnd
}
}
}
+
+ private static void UndoRgba128Bit(Span pixelBytes, int width, bool isBigEndian)
+ {
+ int rowBytesCount = width * 16;
+ int height = pixelBytes.Length / rowBytesCount;
+ if (isBigEndian)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ int offset = 0;
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+ uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+ uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+ uint a = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+
+ for (int x = 1; x < width; x++)
+ {
+ Span rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan);
+ r += deltaR;
+ BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r);
+ offset += 4;
+
+ rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan);
+ g += deltaG;
+ BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g);
+ offset += 4;
+
+ rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan);
+ b += deltaB;
+ BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b);
+ offset += 4;
+
+ rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaA = TiffUtils.ConvertToUIntBigEndian(rowSpan);
+ a += deltaA;
+ BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a);
+ offset += 4;
+ }
+ }
+ }
+ else
+ {
+ for (int y = 0; y < height; y++)
+ {
+ int offset = 0;
+ Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
+ uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+ uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+ uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+ uint a = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
+ offset += 4;
+
+ for (int x = 1; x < width; x++)
+ {
+ Span rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
+ r += deltaR;
+ BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r);
+ offset += 4;
+
+ rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
+ g += deltaG;
+ BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g);
+ offset += 4;
+
+ rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
+ b += deltaB;
+ BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b);
+ offset += 4;
+
+ rowSpan = rowBytes.Slice(offset, 4);
+ uint deltaA = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
+ a += deltaA;
+ BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a);
+ offset += 4;
+ }
+ }
+ }
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
index 5e5e1b7781..bd300f799a 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
@@ -287,6 +287,7 @@ public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider p
[Theory]
[WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)]
+ [WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
@@ -425,12 +426,16 @@ public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
+ [WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)]
+ [WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 9d37a0de47..5ed0a12f7d 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -865,6 +865,7 @@ public static class Tiff
public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff";
public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff";
public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff";
+ public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff";
public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff";
public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff";
public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff";
@@ -874,6 +875,8 @@ public static class Tiff
public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff";
+ public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff";
+ public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff";
public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff";
@@ -882,6 +885,8 @@ public static class Tiff
public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff";
public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff";
public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff";
+ public const string Rgba32BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff";
+ public const string Rgba32BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff";
public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff";
public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff";
diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff
new file mode 100644
index 0000000000..9d985a33a2
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:874ef7a59491ba68364312b7bc27b6620d15ce8b1d5b780f57c6e6d8b919ef1f
+size 73922
diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff
new file mode 100644
index 0000000000..b195cc15a1
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d4faa8617d10ea5f79225c528c0a6d5c36f73d315e46150703df5ca5008ea1bd
+size 73922
diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff
new file mode 100644
index 0000000000..492a8fae8f
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6a17791068b9c3eb40db3157a9103892aaf4a5a74072c93006bfa702ba5545e5
+size 80428
diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff
new file mode 100644
index 0000000000..43075dc21e
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:63fef29d79f8d707c74b6e083de6bb2ad41dde1d9b1aea5bd7729a2f7399132e
+size 80344
diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff
new file mode 100644
index 0000000000..557b6216aa
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9d91f0740d6df983b5e5fe904c22fe86c2a7ffd86673fb078092d80c96359fc1
+size 53666