Browse Source

Merge pull request #2047 from SixLabors/bp/predictorwithalpha

TIFF: Add support for horizontal predictor with alpha data
pull/2058/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
a5cb2ac10b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 222
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  2. 5
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  3. 5
      tests/ImageSharp.Tests/TestImages.cs
  4. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff
  5. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff
  6. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff
  7. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff
  8. 3
      tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff

222
src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param> /// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
/// <param name="width">The width of the image or strip.</param> /// <param name="width">The width of the image or strip.</param>
/// <param name="colorType">The color type of the pixel data.</param> /// <param name="colorType">The color type of the pixel data.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">If set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public static void Undo(Span<byte> pixelBytes, int width, TiffColorType colorType, bool isBigEndian) public static void Undo(Span<byte> pixelBytes, int width, TiffColorType colorType, bool isBigEndian)
{ {
switch (colorType) switch (colorType)
@ -43,12 +43,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case TiffColorType.Rgb888: case TiffColorType.Rgb888:
UndoRgb24Bit(pixelBytes, width); UndoRgb24Bit(pixelBytes, width);
break; break;
case TiffColorType.Rgba8888:
UndoRgba32Bit(pixelBytes, width);
break;
case TiffColorType.Rgb161616: case TiffColorType.Rgb161616:
UndoRgb48Bit(pixelBytes, width, isBigEndian); UndoRgb48Bit(pixelBytes, width, isBigEndian);
break; break;
case TiffColorType.Rgba16161616:
UndoRgba64Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgb323232: case TiffColorType.Rgb323232:
UndoRgb96Bit(pixelBytes, width, isBigEndian); UndoRgb96Bit(pixelBytes, width, isBigEndian);
break; break;
case TiffColorType.Rgba32323232:
UndoRgba128Bit(pixelBytes, width, isBigEndian);
break;
} }
} }
@ -243,6 +252,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
private static void UndoRgba32Bit(Span<byte> pixelBytes, int width)
{
int rowBytesCount = width * 4;
int height = pixelBytes.Length / rowBytesCount;
for (int y = 0; y < height; y++)
{
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
Span<Rgba32> rowRgb = MemoryMarshal.Cast<byte, Rgba32>(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<byte> pixelBytes, int width, bool isBigEndian) private static void UndoRgb48Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{ {
int rowBytesCount = width * 6; int rowBytesCount = width * 6;
@ -319,6 +355,98 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
private static void UndoRgba64Bit(Span<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> pixelBytes, int width, bool isBigEndian) private static void UndoRgb96Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{ {
int rowBytesCount = width * 12; int rowBytesCount = width * 12;
@ -394,5 +522,97 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
} }
private static void UndoRgba128Bit(Span<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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;
}
}
}
}
} }
} }

5
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -287,6 +287,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory] [Theory]
[WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)] [WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)]
[WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -425,12 +426,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory] [Theory]
[WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] [WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] [WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba16BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)]
[WithFile(Rgba16BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] [WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] [WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)]
[WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {

5
tests/ImageSharp.Tests/TestImages.cs

@ -865,6 +865,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff";
public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff";
public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.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 Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff";
public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff";
public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff";
@ -874,6 +875,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.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 Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff"; public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff";
public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff";
@ -882,6 +885,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff"; public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff";
public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff";
public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.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 Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff";
public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff"; public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff";

3
tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:874ef7a59491ba68364312b7bc27b6620d15ce8b1d5b780f57c6e6d8b919ef1f
size 73922

3
tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d4faa8617d10ea5f79225c528c0a6d5c36f73d315e46150703df5ca5008ea1bd
size 73922

3
tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a17791068b9c3eb40db3157a9103892aaf4a5a74072c93006bfa702ba5545e5
size 80428

3
tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63fef29d79f8d707c74b6e083de6bb2ad41dde1d9b1aea5bd7729a2f7399132e
size 80344

3
tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d91f0740d6df983b5e5fe904c22fe86c2a7ffd86673fb078092d80c96359fc1
size 53666
Loading…
Cancel
Save