Browse Source

Merge branch 'main' into dp/jpeg-downscaling-decode

pull/2076/head
Dmitry Pentin 4 years ago
parent
commit
3f16a6836f
  1. 6
      .github/dependabot.yml
  2. 14
      .github/workflows/build-and-test.yml
  3. 12
      .github/workflows/code-coverage.yml
  4. 8
      src/ImageSharp/Common/Helpers/Numerics.cs
  5. 5
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs
  6. 7
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
  7. 26
      src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs
  8. 124
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  9. 3
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  10. 30
      src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
  11. 60
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
  12. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs
  13. 12
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs
  14. 6
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  15. 15
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
  16. 30
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  17. 10
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  18. 144
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  19. 31
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  20. 108
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
  21. 45
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  22. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  23. 67
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs
  24. 9
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  25. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  26. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  27. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs
  28. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  29. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  30. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  31. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  32. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  33. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  34. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  35. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  36. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
  37. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
  38. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
  39. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
  40. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
  41. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
  42. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
  43. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  44. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  45. 67
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs
  46. 9
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  47. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  48. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  49. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs
  50. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs
  51. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  52. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  53. 8
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  54. 31
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  55. 2
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  56. 92
      src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs
  57. 96
      src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
  58. 147
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  59. 37
      src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
  60. 105
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  61. 6
      src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
  62. 10
      src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
  63. 89
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  64. 43
      src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
  65. 2
      tests/Directory.Build.targets
  66. 1
      tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs
  67. 39
      tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs
  68. 8
      tests/ImageSharp.Benchmarks/Config.cs
  69. 26
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  70. 19
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  71. 14
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  72. 22
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  73. 10
      tests/ImageSharp.Tests/TestImages.cs
  74. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h1v1.png
  75. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v1.png
  76. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v2.png
  77. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h4v4.png
  78. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-planar-08_h1v1.png
  79. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h2v2.png
  80. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h4v4.png
  81. 3
      tests/Images/Input/Jpg/issues/Issue2133.jpg
  82. 3
      tests/Images/Input/Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg
  83. 3
      tests/Images/Input/Tiff/CCITTGroup4.tiff
  84. 3
      tests/Images/Input/Tiff/CCITTGroup4_minisblack.tiff
  85. 3
      tests/Images/Input/Tiff/Issues/Group4CompressionWithStrips.tiff
  86. 3
      tests/Images/Input/Tiff/Issues/Issue2123.tiff
  87. 3
      tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v1.tiff
  88. 4
      tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff
  89. 3
      tests/Images/Input/Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff
  90. 3
      tests/Images/Input/Tiff/ycbcr_jpegcompressed2.tiff

6
.github/dependabot.yml

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

14
.github/workflows/build-and-test.yml

@ -58,7 +58,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
@ -68,7 +68,7 @@ jobs:
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Git Setup LFS Cache
uses: actions/cache@v2
uses: actions/cache@v3
id: lfs-cache
with:
path: .git/lfs
@ -81,7 +81,7 @@ jobs:
uses: NuGet/setup-nuget@v1
- name: NuGet Setup Cache
uses: actions/cache@v2
uses: actions/cache@v3
id: nuget-cache
with:
path: ~/.nuget
@ -89,7 +89,7 @@ jobs:
restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Setup
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v2
with:
include-prerelease: true
dotnet-version: |
@ -127,7 +127,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
@ -148,7 +148,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
@ -157,7 +157,7 @@ jobs:
uses: NuGet/setup-nuget@v1
- name: NuGet Setup Cache
uses: actions/cache@v2
uses: actions/cache@v3
id: nuget-cache
with:
path: ~/.nuget

12
.github/workflows/code-coverage.yml

@ -24,7 +24,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
@ -34,7 +34,7 @@ jobs:
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Git Setup LFS Cache
uses: actions/cache@v2
uses: actions/cache@v3
id: lfs-cache
with:
path: .git/lfs
@ -47,7 +47,7 @@ jobs:
uses: NuGet/setup-nuget@v1
- name: NuGet Setup Cache
uses: actions/cache@v2
uses: actions/cache@v3
id: nuget-cache
with:
path: ~/.nuget
@ -55,7 +55,7 @@ jobs:
restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Setup
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v2
with:
dotnet-version: |
6.0.x
@ -74,14 +74,14 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/
- name: Codecov Update
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v3
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests

8
src/ImageSharp/Common/Helpers/Numerics.cs

@ -75,6 +75,12 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo8(int x) => x & 7;
/// <summary>
/// Calculates <paramref name="x"/> % 8
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo8(nint x) => x & 7;
/// <summary>
/// Fast (x mod m) calculator, with the restriction that
/// <paramref name="m"/> should be power of 2.
@ -968,7 +974,7 @@ namespace SixLabors.ImageSharp
/// Tells whether input value is outside of the given range.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="min">Mininum value, inclusive.</param>
/// <param name="min">Minimum value, inclusive.</param>
/// <param name="max">Maximum value, inclusive.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOutOfRange(int value, int min, int max)

5
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs

@ -35,8 +35,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[FieldOffset(224)]
public Vector256<float> V7;
private static readonly Vector256<int> MultiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7);
private static unsafe void MultiplyIntoInt16_Avx2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!");
@ -45,6 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
ref Vector256<float> bBase = ref b.V0;
ref Vector256<short> destRef = ref dest.V01;
Vector256<int> multiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7);
for (nint i = 0; i < 8; i += 2)
{
@ -52,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Vector256<int> row1 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1)));
Vector256<short> row = Avx2.PackSignedSaturate(row0, row1);
row = Avx2.PermuteVar8x32(row.AsInt32(), MultiplyIntoInt16ShuffleMask).AsInt16();
row = Avx2.PermuteVar8x32(row.AsInt32(), multiplyIntoInt16ShuffleMask).AsInt16();
Unsafe.Add(ref destRef, (IntPtr)((uint)i / 2)) = row;
}

7
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs

@ -155,12 +155,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
c = this.ReadStream();
}
// Found a marker
// We accept multiple FF bytes followed by a 0 as meaning a single FF data byte.
// This data pattern is not valid according to the standard.
// even though it's considered 'invalid' according to the specs.
if (c != 0)
{
this.Marker = (byte)c;
// It's a trick so we won't read past actual marker
this.badData = true;
this.Marker = (byte)c;
this.MarkerPosition = this.stream.Position - 2;
}
}
@ -199,7 +201,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (b != 0)
{
this.Marker = (byte)b;
this.badData = true;
this.MarkerPosition = this.stream.Position - 2;
return true;
}

26
src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs

@ -9,18 +9,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal static partial class FloatingPointDCT
{
#pragma warning disable SA1310, SA1311, IDE1006 // naming rule violation warnings
private static readonly Vector256<float> mm256_F_0_7071 = Vector256.Create(0.707106781f);
private static readonly Vector256<float> mm256_F_0_3826 = Vector256.Create(0.382683433f);
private static readonly Vector256<float> mm256_F_0_5411 = Vector256.Create(0.541196100f);
private static readonly Vector256<float> mm256_F_1_3065 = Vector256.Create(1.306562965f);
private static readonly Vector256<float> mm256_F_1_4142 = Vector256.Create(1.414213562f);
private static readonly Vector256<float> mm256_F_1_8477 = Vector256.Create(1.847759065f);
private static readonly Vector256<float> mm256_F_n1_0823 = Vector256.Create(-1.082392200f);
private static readonly Vector256<float> mm256_F_n2_6131 = Vector256.Create(-2.613125930f);
#pragma warning restore SA1310, SA1311, IDE1006
/// <summary>
/// Apply floating point FDCT inplace using simd operations.
/// </summary>
@ -57,6 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
block.V0 = Avx.Add(tmp10, tmp11);
block.V4 = Avx.Subtract(tmp10, tmp11);
Vector256<float> mm256_F_0_7071 = Vector256.Create(0.707106781f);
Vector256<float> z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071);
block.V2 = Avx.Add(tmp13, z1);
block.V6 = Avx.Subtract(tmp13, z1);
@ -66,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
tmp11 = Avx.Add(tmp5, tmp6);
tmp12 = Avx.Add(tmp6, tmp7);
Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826);
Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10);
Vector256<float> z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12);
Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), Vector256.Create(0.382683433f)); // mm256_F_0_3826
Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(0.541196100f), tmp10); // mm256_F_0_5411
Vector256<float> z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(1.306562965f), tmp12); // mm256_F_1_3065
Vector256<float> z3 = Avx.Multiply(tmp11, mm256_F_0_7071);
Vector256<float> z11 = Avx.Add(tmp7, z3);
@ -109,6 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Vector256<float> tmp10 = Avx.Add(z5, tmp2);
Vector256<float> tmp11 = Avx.Subtract(z5, tmp2);
Vector256<float> mm256_F_1_4142 = Vector256.Create(1.414213562f);
Vector256<float> tmp13 = Avx.Add(tmp1, tmp3);
Vector256<float> tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142);
@ -131,10 +121,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
tmp7 = Avx.Add(z11, z13);
tmp11 = Avx.Multiply(Avx.Subtract(z11, z13), mm256_F_1_4142);
z5 = Avx.Multiply(Avx.Add(z10, z12), mm256_F_1_8477);
z5 = Avx.Multiply(Avx.Add(z10, z12), Vector256.Create(1.847759065f)); // mm256_F_1_8477
tmp10 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z12, mm256_F_n1_0823);
tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, mm256_F_n2_6131);
tmp10 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z12, Vector256.Create(-1.082392200f)); // mm256_F_n1_0823
tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, Vector256.Create(-2.613125930f)); // mm256_F_n2_6131
tmp6 = Avx.Subtract(tmp12, tmp7);
tmp5 = Avx.Subtract(tmp11, tmp6);

124
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -90,6 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private JFifMarker jFif;
/// <summary>
/// Whether the image has a JFIF marker. This is needed to determine, if the colorspace is YCbCr.
/// </summary>
private bool hasJFif;
/// <summary>
/// Contains information about the Adobe marker.
/// </summary>
@ -159,38 +164,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Finds the next file marker within the byte stream.
/// </summary>
/// <param name="marker">The buffer to read file markers to.</param>
/// <param name="stream">The input stream.</param>
/// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream)
/// <returns>The <see cref="JpegFileMarker"/>.</returns>
public static JpegFileMarker FindNextFileMarker(BufferedReadStream stream)
{
int value = stream.Read(marker, 0, 2);
if (value == 0)
while (true)
{
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
int b = stream.ReadByte();
if (b == -1)
{
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
if (marker[0] == JpegConstants.Markers.XFF)
{
// According to Section B.1.1.2:
// "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF."
int m = marker[1];
while (m == JpegConstants.Markers.XFF)
// Found a marker.
if (b == JpegConstants.Markers.XFF)
{
int suffix = stream.ReadByte();
if (suffix == -1)
while (b == JpegConstants.Markers.XFF)
{
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
// Loop here to discard any padding FF bytes on terminating marker.
b = stream.ReadByte();
if (b == -1)
{
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
}
m = suffix;
// Found a valid marker. Exit loop
if (b is not 0 and (< JpegConstants.Markers.RST0 or > JpegConstants.Markers.RST7))
{
return new JpegFileMarker((byte)(uint)b, stream.Position - 2);
}
}
return new JpegFileMarker((byte)m, stream.Position - 2);
}
return new JpegFileMarker(marker[1], stream.Position - 2, true);
}
/// <inheritdoc/>
@ -340,15 +345,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
}
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
fileMarker = FindNextFileMarker(stream);
this.QuantizationTables ??= new Block8x8F[4];
// Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695
while (fileMarker.Marker != JpegConstants.Markers.EOI
|| (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
while (fileMarker.Marker != JpegConstants.Markers.EOI)
{
cancellationToken.ThrowIfCancellationRequested();
@ -500,7 +502,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, stream);
fileMarker = FindNextFileMarker(stream);
}
}
@ -528,17 +530,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentCount == 3)
{
if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
// We prioritize adobe marker over jfif marker, if somebody really encoded this image with redundant adobe marker,
// then it's most likely an adobe jfif image.
if (!this.adobe.Equals(default))
{
return JpegColorSpace.RGB;
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr)
{
return JpegColorSpace.YCbCr;
}
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
return JpegColorSpace.RGB;
}
// Fallback to the id color deduction: If these values are 1-3 for a 3-channel image, then the image is assumed to be YCbCr.
if (this.Components[2].Id == 3 && this.Components[1].Id == 2 && this.Components[0].Id == 1)
{
return JpegColorSpace.YCbCr;
}
JpegThrowHelper.ThrowNotSupportedColorSpace();
}
if (this.hasJFif)
{
// JFIF implies YCbCr.
return JpegColorSpace.YCbCr;
}
// Fallback to the id color deduction.
// If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr.
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color
if (this.Components[2].Id == 66 && this.Components[1].Id == 71 && this.Components[0].Id == 82)
{
return JpegColorSpace.RGB;
}
// If these values are 1-3 for a 3-channel image, then the image is assumed to be YCbCr.
if (this.Components[2].Id == 3 && this.Components[1].Id == 2 && this.Components[0].Id == 1)
{
return JpegColorSpace.YCbCr;
}
// 3-channel non-subsampled images are assumed to be RGB.
if (this.Components[2].VerticalSamplingFactor == 1 && this.Components[1].VerticalSamplingFactor == 1 && this.Components[0].VerticalSamplingFactor == 1 &&
this.Components[2].HorizontalSamplingFactor == 1 && this.Components[1].HorizontalSamplingFactor == 1 && this.Components[0].HorizontalSamplingFactor == 1)
{
return JpegColorSpace.RGB;
}
// Some images are poorly encoded and contain incorrect colorspace transform metadata.
// We ignore that and always fall back to the default colorspace.
return JpegColorSpace.YCbCr;
@ -546,9 +587,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentCount == 4)
{
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
? JpegColorSpace.Ycck
: JpegColorSpace.Cmyk;
// jfif images doesn't not support 4 component images, so we only check adobe.
if (!this.adobe.Equals(default))
{
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck)
{
return JpegColorSpace.Ycck;
}
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
return JpegColorSpace.Cmyk;
}
JpegThrowHelper.ThrowNotSupportedColorSpace();
}
// Fallback to cmyk as neither of cmyk nor ycck have 'special' component ids.
return JpegColorSpace.Cmyk;
}
JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount);
@ -715,6 +771,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining)
{
this.hasJFif = true;
// We can only decode JFif identifiers.
// Some images contain multiple JFIF markers (Issue 1932) so we check to see
// if it's already been read.

3
src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

@ -51,5 +51,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedColorSpace() => throw new NotSupportedException("Image color space could not be deduced.");
}
}

30
src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs

@ -2,21 +2,23 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal static class BitWriterUtils
{
public static void WriteBits(Span<byte> buffer, int pos, uint count, byte value)
public static void WriteBits(Span<byte> buffer, nint pos, nint count, byte value)
{
int bitPos = pos % 8;
int bufferPos = pos / 8;
int startIdx = bufferPos + bitPos;
int endIdx = (int)(startIdx + count);
nint bitPos = Numerics.Modulo8(pos);
nint bufferPos = pos / 8;
nint startIdx = bufferPos + bitPos;
nint endIdx = startIdx + count;
if (value == 1)
{
for (int i = startIdx; i < endIdx; i++)
for (nint i = startIdx; i < endIdx; i++)
{
WriteBit(buffer, bufferPos, bitPos);
@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
else
{
for (int i = startIdx; i < endIdx; i++)
for (nint i = startIdx; i < endIdx; i++)
{
WriteZeroBit(buffer, bufferPos, bitPos);
@ -44,8 +46,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
public static void WriteBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos));
[MethodImpl(InliningOptions.ShortMethod)]
public static void WriteBit(Span<byte> buffer, nint bufferPos, nint bitPos)
{
ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos);
b |= (byte)(1 << (int)(7 - bitPos));
}
public static void WriteZeroBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos)));
[MethodImpl(InliningOptions.ShortMethod)]
public static void WriteZeroBit(Span<byte> buffer, nint bufferPos, nint bitPos)
{
ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos);
b = (byte)(b & ~(1 << (int)(7 - bitPos)));
}
}
}

60
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs

@ -23,28 +23,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560
};
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new()
{
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new()
{
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new()
{
{ 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B }
};
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new()
{
{ 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 },
{ 27, 0x24 }, { 28, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new()
{
{ 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 },
{ 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D },
@ -53,57 +53,57 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{ 63, 0x34 }
};
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new()
{
{ 2, 0x3 }, { 3, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new()
{
{ 1, 0x2 }, { 4, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new()
{
{ 5, 0x3 }, { 6, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new()
{
{ 7, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new()
{
{ 8, 0x5 }, { 9, 0x4 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new()
{
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new()
{
{ 13, 0x4 }, { 14, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new()
{
{ 15, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new()
{
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new()
{
{ 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new()
{
{ 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 },
{ 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB },
@ -112,62 +112,62 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{ 62, 0x66 }, { 63, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new()
{
{ 64, 0x1B }, { 128, 0x12 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new()
{
{ 192, 0x17 }, { 1664, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new()
{
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new()
{
{ 256, 0x37 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new()
{
{ 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 },
{ 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 },
{ 1600, 0x9A }, { 1728, 0x9B }
};
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new()
{
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new()
{
{ 64, 0xF }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new()
{
{ 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 },
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F }
};
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new()
{
{ 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 },
{ 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 },
@ -442,16 +442,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
}
/// <summary>
/// Pads output to the next byte
/// Pads output to the next byte.
/// </summary>
/// <remarks>
/// If the output is not currently on a byte boundary,
/// zero-pad it to the next byte
/// zero-pad it to the next byte.
/// </remarks>
protected void PadByte()
{
// Check if padding is necessary.
if (this.bitPosition % 8 != 0)
if (Numerics.Modulo8(this.bitPosition) != 0)
{
// Skip padding bits, move to next byte.
this.bytePosition++;

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -130,6 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return index + offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int FindB2ForImaginaryWhiteLine() => this.width;
private int FindB2ForNormalLine(int b1)

12
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs

@ -13,15 +13,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="code">The code word.</param>
/// <param name="type">The type of the code.</param>
/// <param name="bitsRequired">The bits required.</param>
/// <param name="extensionBits">The extension bits.</param>
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
public CcittTwoDimensionalCode(int code, CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
{
this.Code = code;
this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
}
/// <summary>
/// Gets the code type.
/// </summary>
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111);
public int Code { get; }
}
}

6
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -65,13 +65,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder?
using var decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}
// If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space.
// There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB).
case TiffPhotometricInterpretation.YCbCr:
case TiffPhotometricInterpretation.Rgb:
{
@ -82,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder?
using var decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}

15
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs

@ -1,9 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -19,14 +18,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
: base(input, fillOrder, bytesToRead)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < 6;
/// <inheritdoc/>
public override bool IsEndOfScanLine
@ -53,12 +51,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
base.StartNewRow();
int remainder = this.BitsRead & 7; // bit-hack for % 8
int remainder = Numerics.Modulo8(this.BitsRead);
if (remainder != 0)
{
// Skip padding bits, move to next byte.
this.Position++;
this.ResetBitsRead();
this.AdvancePosition();
}
}

30
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

@ -42,11 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator);
var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount);
buffer.Clear();
uint bitsWritten = 0;
uint pixelsWritten = 0;
nint bitsWritten = 0;
nuint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
@ -55,32 +56,39 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue);
}
bitsWritten += bitReader.RunLength;
bitsWritten += (int)bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (pixelsWritten == this.Width)
if (pixelsWritten == (ulong)this.Width)
{
bitReader.StartNewRow();
rowsWritten++;
pixelsWritten = 0;
// Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8);
nint pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad;
}
if (rowsWritten >= stripHeight)
{
break;
}
bitReader.StartNewRow();
}
if (pixelsWritten > this.Width)
if (pixelsWritten > (ulong)this.Width)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width");
}

10
src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
byte repeatData = compressedData[compressedOffset + 1];
int repeatLength = 257 - headerByte;
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
buffer.Slice(decompressedOffset, repeatLength).Fill(repeatData);
compressedOffset += 2;
decompressedOffset += repeatLength;
@ -81,14 +81,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
}
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length)
{
for (int i = 0; i < length; i++)
{
destinationArray[i + destinationIndex] = value;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose();
}

144
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs

@ -1,20 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bitreader for reading compressed CCITT T4 1D data.
/// </summary>
internal class T4BitReader : IDisposable
internal class T4BitReader
{
/// <summary>
/// The logical order of bits within a byte.
@ -52,28 +49,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
private readonly int maxCodeLength = 13;
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new()
{
{ 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new()
{
{ 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new()
{
{ 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 }
};
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new()
{
{ 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 },
{ 0x24, 27 }, { 0x18, 28 }
};
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new()
{
{ 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 },
{ 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 },
@ -81,57 +78,57 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 }
};
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new()
{
{ 0x3, 2 }, { 0x2, 3 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new()
{
{ 0x2, 1 }, { 0x3, 4 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new()
{
{ 0x3, 5 }, { 0x2, 6 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new()
{
{ 0x3, 7 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new()
{
{ 0x5, 8 }, { 0x4, 9 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new()
{
{ 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new()
{
{ 0x4, 13 }, { 0x7, 14 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new()
{
{ 0x18, 15 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new()
{
{ 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new()
{
{ 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 }
};
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new()
{
{ 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 },
{ 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 },
@ -140,82 +137,84 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ 0x66, 62 }, { 0x67, 63 }
};
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new()
{
{ 0x1B, 64 }, { 0x12, 128 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new()
{
{ 0x17, 192 }, { 0x18, 1664 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new()
{
{ 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new()
{
{ 0x37, 256 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new()
{
{ 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 },
{ 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 },
{ 0x9A, 1600 }, { 0x9B, 1728 }
};
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new()
{
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new()
{
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new()
{
{ 0xF, 64 }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new()
{
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new()
{
{ 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 },
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
};
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new()
{
{ 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 },
{ 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 },
{ 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 }
};
/// <summary>
/// The compressed input stream.
/// </summary>
private readonly BufferedReadStream stream;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitReader" /> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false)
public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, bool eolPadding = false)
{
this.stream = input;
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.DataLength = bytesToRead;
this.BitsRead = 0;
this.Value = 0;
@ -228,12 +227,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.RunLength = 0;
this.eolPadding = eolPadding;
this.ReadNextByte();
if (this.eolPadding)
{
this.maxCodeLength = 24;
}
}
/// <summary>
/// Gets or sets the byte at the given position.
/// </summary>
private byte DataAtPosition { get; set; }
/// <summary>
/// Gets the current value.
/// </summary>
@ -259,11 +265,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
protected ulong Position { get; set; }
/// <summary>
/// Gets the compressed image data.
/// </summary>
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Gets a value indicating whether there is more data to read left.
/// </summary>
@ -390,9 +391,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.terminationCodeFound = false;
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary>
/// An EOL is expected before the first data.
/// </summary>
@ -436,6 +434,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
/// <param name="nBits">The number of bits to read.</param>
/// <returns>The value read.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected uint ReadValue(int nBits)
{
DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits));
@ -452,6 +451,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return v;
}
/// <summary>
/// Advances the position by one byte.
/// </summary>
/// <returns>True, if data could be advanced by one byte, otherwise false.</returns>
protected bool AdvancePosition()
{
if (this.LoadNewByte())
{
return true;
}
return false;
}
private uint WhiteTerminatingCodeRunLength()
{
switch (this.CurValueBitsRead)
@ -806,44 +819,49 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint GetBit()
{
if (this.BitsRead >= 8)
{
this.LoadNewByte();
this.AdvancePosition();
}
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.BitsRead - 1;
uint bit = (uint)((dataSpan[(int)this.Position] & (1 << shift)) != 0 ? 1 : 0);
uint bit = (uint)((this.DataAtPosition & (1 << shift)) != 0 ? 1 : 0);
this.BitsRead++;
return bit;
}
private void LoadNewByte()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool LoadNewByte()
{
this.Position++;
this.ResetBitsRead();
if (this.Position >= (ulong)this.DataLength)
if (this.Position < (ulong)this.DataLength)
{
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid ccitt compressed data");
this.ReadNextByte();
this.Position++;
return true;
}
this.Position++;
this.DataAtPosition = 0;
return false;
}
private void ReadImageDataFromStream(Stream input, int bytesToRead)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadNextByte()
{
Span<byte> dataSpan = this.Data.GetSpan();
input.Read(dataSpan, 0, bytesToRead);
if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst)
int nextByte = this.stream.ReadByte();
if (nextByte == -1)
{
for (int i = 0; i < dataSpan.Length; i++)
{
dataSpan[i] = ReverseBits(dataSpan[i]);
}
TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data.");
}
this.ResetBitsRead();
this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst
? ReverseBits((byte)nextByte)
: (byte)nextByte;
}
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits

31
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs

@ -61,11 +61,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding);
var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, eolPadding);
buffer.Clear();
uint bitsWritten = 0;
uint pixelWritten = 0;
nint bitsWritten = 0;
nuint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
@ -74,41 +75,47 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
this.WritePixelRun(buffer, bitReader, bitsWritten);
bitsWritten += bitReader.RunLength;
pixelWritten += bitReader.RunLength;
bitsWritten += (int)bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (bitReader.IsEndOfScanLine)
{
// Write padding bytes, if necessary.
uint pad = 8 - (bitsWritten % 8);
nint pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad;
}
pixelWritten = 0;
pixelsWritten = 0;
rowsWritten++;
if (rowsWritten >= stripHeight)
{
break;
}
}
}
// Edge case for when we are at the last byte, but there are still some unwritten pixels left.
if (pixelWritten > 0 && pixelWritten < this.width)
if (pixelsWritten > 0 && pixelsWritten < (ulong)this.width)
{
bitReader.ReadNextRun();
this.WritePixelRun(buffer, bitReader, bitsWritten);
}
}
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, uint bitsWritten)
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, nint bitsWritten)
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue);
}
}

108
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs

@ -1,10 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -16,38 +15,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
private readonly int maxCodeLength = 12;
private static readonly CcittTwoDimensionalCode None = new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.None, 0);
private static readonly CcittTwoDimensionalCode None = new(0, CcittTwoDimensionalCodeType.None, 0);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len1Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) }
};
private static readonly CcittTwoDimensionalCode Len1Code1 = new(0b1, CcittTwoDimensionalCodeType.Vertical0, 1);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len3Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) },
{ 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) },
{ 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) }
};
private static readonly CcittTwoDimensionalCode Len3Code001 = new(0b001, CcittTwoDimensionalCodeType.Horizontal, 3);
private static readonly CcittTwoDimensionalCode Len3Code010 = new(0b010, CcittTwoDimensionalCodeType.VerticalL1, 3);
private static readonly CcittTwoDimensionalCode Len3Code011 = new(0b011, CcittTwoDimensionalCodeType.VerticalR1, 3);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len4Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) }
};
private static readonly CcittTwoDimensionalCode Len4Code0001 = new(0b0001, CcittTwoDimensionalCodeType.Pass, 4);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len6Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) },
{ 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) }
};
private static readonly CcittTwoDimensionalCode Len6Code000011 = new(0b000011, CcittTwoDimensionalCodeType.VerticalR2, 6);
private static readonly CcittTwoDimensionalCode Len6Code000010 = new(0b000010, CcittTwoDimensionalCodeType.VerticalL2, 6);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len7Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) },
{ 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) },
{ 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) },
{ 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) }
};
private static readonly CcittTwoDimensionalCode Len7Code0000011 = new(0b0000011, CcittTwoDimensionalCodeType.VerticalR3, 7);
private static readonly CcittTwoDimensionalCode Len7Code0000010 = new(0b0000010, CcittTwoDimensionalCodeType.VerticalL3, 7);
private static readonly CcittTwoDimensionalCode Len7Code0000001 = new(0b0000001, CcittTwoDimensionalCodeType.Extensions2D, 7);
private static readonly CcittTwoDimensionalCode Len7Code0000000 = new(0b0000000, CcittTwoDimensionalCodeType.Extensions1D, 7);
/// <summary>
/// Initializes a new instance of the <see cref="T6BitReader"/> class.
@ -55,14 +39,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
: base(input, fillOrder, bytesToRead)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < (7 - 1);
/// <summary>
/// Gets or sets the two dimensional code.
@ -85,45 +68,81 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
switch (this.CurValueBitsRead)
{
case 1:
if (Len1Codes.ContainsKey(value))
if (value == Len1Code1.Code)
{
this.Code = Len1Codes[value];
this.Code = Len1Code1;
return false;
}
break;
case 3:
if (Len3Codes.ContainsKey(value))
if (value == Len3Code001.Code)
{
this.Code = Len3Codes[value];
this.Code = Len3Code001;
return false;
}
if (value == Len3Code010.Code)
{
this.Code = Len3Code010;
return false;
}
if (value == Len3Code011.Code)
{
this.Code = Len3Code011;
return false;
}
break;
case 4:
if (Len4Codes.ContainsKey(value))
if (value == Len4Code0001.Code)
{
this.Code = Len4Codes[value];
this.Code = Len4Code0001;
return false;
}
break;
case 6:
if (Len6Codes.ContainsKey(value))
if (value == Len6Code000010.Code)
{
this.Code = Len6Codes[value];
this.Code = Len6Code000010;
return false;
}
if (value == Len6Code000011.Code)
{
this.Code = Len6Code000011;
return false;
}
break;
case 7:
if (Len7Codes.ContainsKey(value))
if (value == Len7Code0000000.Code)
{
this.Code = Len7Code0000000;
return false;
}
if (value == Len7Code0000001.Code)
{
this.Code = Len7Code0000001;
return false;
}
if (value == Len7Code0000011.Code)
{
this.Code = Len7Code0000011;
return false;
}
if (value == Len7Code0000010.Code)
{
this.Code = Len7Codes[value];
this.Code = Len7Code0000010;
return false;
}
@ -154,6 +173,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Swaps the white run to black run an vise versa.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun;
}
}

45
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -15,12 +17,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
private readonly bool isWhiteZero;
private readonly byte whiteValue;
private readonly byte blackValue;
private readonly int width;
private readonly byte white;
/// <summary>
/// Initializes a new instance of the <see cref="T6TiffCompression" /> class.
/// </summary>
@ -40,8 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.FillOrder = fillOrder;
this.width = width;
this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1);
this.blackValue = (byte)(this.isWhiteZero ? 1 : 0);
this.white = (byte)(this.isWhiteZero ? 0 : 255);
}
/// <summary>
@ -53,15 +52,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
int height = stripHeight;
buffer.Clear();
using System.Buffers.IMemoryOwner<byte> scanLineBuffer = this.Allocator.Allocate<byte>(this.width * 2);
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width);
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width);
using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator);
var bitReader = new T6BitReader(stream, this.FillOrder, byteCount);
var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width);
uint bitsWritten = 0;
nint bitsWritten = 0;
for (int y = 0; y < height; y++)
{
scanLine.Clear();
@ -74,21 +74,34 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
}
private uint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, uint bitsWritten)
private nint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, nint bitsWritten)
{
byte white = (byte)(this.isWhiteZero ? 0 : 255);
for (int i = 0; i < scanLine.Length; i++)
nint bitPos = Numerics.Modulo8(bitsWritten);
nint bufferPos = bitsWritten / 8;
ref byte scanLineRef = ref MemoryMarshal.GetReference(scanLine);
for (nint i = 0; i < scanLine.Length; i++)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, 1, scanLine[i] == white ? this.whiteValue : this.blackValue);
if (Unsafe.Add(ref scanLineRef, i) != this.white)
{
BitWriterUtils.WriteBit(buffer, bufferPos, bitPos);
}
bitPos++;
bitsWritten++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
}
// Write padding bytes, if necessary.
uint remainder = bitsWritten % 8;
nint remainder = Numerics.Modulo8(bitsWritten);
if (remainder != 0)
{
uint padding = 8 - remainder;
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, padding, 0);
nint padding = 8 - remainder;
BitWriterUtils.WriteBits(buffer, bitsWritten, padding, 0);
bitsWritten += padding;
}
@ -122,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
else
{
scanline.Fill((byte)255);
scanline.Fill(255);
}
break;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

67
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -17,26 +19,65 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
nint offset = 0;
var colorBlack = default(TPixel);
var colorWhite = default(TPixel);
int offset = 0;
Color black = Color.Black;
Color white = Color.White;
for (int y = top; y < top + height; y++)
colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
ref byte dataRef = ref MemoryMarshal.GetReference(data);
for (nint y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x += 8)
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)y);
ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan);
for (nint x = left; x < left + width; x += 8)
{
byte b = data[offset++];
int maxShift = Math.Min(left + width - x, 8);
byte b = Unsafe.Add(ref dataRef, offset++);
nint maxShift = Math.Min(left + width - x, 8);
for (int shift = 0; shift < maxShift; shift++)
if (maxShift == 8)
{
int bit = (b >> (7 - shift)) & 1;
int bit = (b >> 7) & 1;
ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x);
pixel0 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 6) & 1;
ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1);
pixel1 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 5) & 1;
ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2);
pixel2 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 4) & 1;
ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3);
pixel3 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 3) & 1;
ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4);
pixel4 = bit == 0 ? colorBlack : colorWhite;
color.FromRgba32(bit == 0 ? black : white);
bit = (b >> 2) & 1;
ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5);
pixel5 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 1) & 1;
ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6);
pixel6 = bit == 0 ? colorBlack : colorWhite;
bit = b & 1;
ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7);
pixel7 = bit == 0 ? colorBlack : colorWhite;
}
else
{
for (int shift = 0; shift < maxShift; shift++)
{
int bit = (b >> (7 - shift)) & 1;
pixels[x + shift, y] = color;
ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift);
pixel = bit == 0 ? colorBlack : colorWhite;
}
}
}
}

9
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,14 +26,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

7
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
@ -32,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
byte intensity2 = (byte)((byteData & 0x0F) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
}
if (isOddWidth)
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[left + width - 1, y] = color;
pixelRowSpan[left + width - 1] = color;
}
}
}

8
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly TPixel[] palette;
private const float InvMax = 1.0f / 65535F;
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap)
@ -56,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int i = 0; i < palette.Length; i++)
{
float r = colorMap[rOffset + i] / 65535F;
float g = colorMap[gOffset + i] / 65535F;
float b = colorMap[bOffset + i] / 65535F;
float r = colorMap[rOffset + i] * InvMax;
float g = colorMap[gOffset + i] * InvMax;
float b = colorMap[bOffset + i] * InvMax;
palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f));
}

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,11 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -26,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs

@ -42,11 +42,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -33,11 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -33,10 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -33,10 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++)

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -26,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,11 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

67
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -16,26 +18,65 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
nint offset = 0;
var colorBlack = default(TPixel);
var colorWhite = default(TPixel);
int offset = 0;
Color black = Color.Black;
Color white = Color.White;
for (int y = top; y < top + height; y++)
colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
ref byte dataRef = ref MemoryMarshal.GetReference(data);
for (nint y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x += 8)
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)y);
ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan);
for (nint x = left; x < left + width; x += 8)
{
byte b = data[offset++];
int maxShift = Math.Min(left + width - x, 8);
byte b = Unsafe.Add(ref dataRef, offset++);
nint maxShift = Math.Min(left + width - x, 8);
for (int shift = 0; shift < maxShift; shift++)
if (maxShift == 8)
{
int bit = (b >> (7 - shift)) & 1;
int bit = (b >> 7) & 1;
ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x);
pixel0 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 6) & 1;
ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1);
pixel1 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 5) & 1;
ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2);
pixel2 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 4) & 1;
ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3);
pixel3 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 3) & 1;
ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4);
pixel4 = bit == 0 ? colorWhite : colorBlack;
color.FromRgba32(bit == 0 ? white : black);
bit = (b >> 2) & 1;
ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5);
pixel5 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 1) & 1;
ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6);
pixel6 = bit == 0 ? colorWhite : colorBlack;
bit = b & 1;
ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7);
pixel7 = bit == 0 ? colorWhite : colorBlack;
}
else
{
for (int shift = 0; shift < maxShift; shift++)
{
int bit = (b >> (7 - shift)) & 1;
pixels[x + shift, y] = color;
ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift);
pixel = bit == 0 ? colorWhite : colorBlack;
}
}
}
}

9
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,15 +26,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
const uint maxValue = 0xFFFFFF;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -26,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
const uint maxValue = 0xFFFFFFFF;
int offset = 0;

7
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
@ -32,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
}
if (isOddWidth)
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[left + width - 1, y] = color;
pixelRowSpan[left + width - 1] = color;
}
}
}

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs

@ -19,16 +19,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private static readonly Rational[] DefaultLuma =
{
new Rational(299, 1000),
new Rational(587, 1000),
new Rational(114, 1000)
new(299, 1000),
new(587, 1000),
new(114, 1000)
};
private static readonly Rational[] DefaultReferenceBlackWhite =
{
new Rational(0, 1), new Rational(255, 1),
new Rational(128, 1), new Rational(255, 1),
new Rational(128, 1), new Rational(255, 1)
new(0, 1), new(255, 1),
new(128, 1), new(255, 1),
new(128, 1), new(255, 1)
};
public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients)

7
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs

@ -39,8 +39,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span<byte> tmpBufferSpan = tmpBuffer.GetSpan();
ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan);
ycbcrData = tmpBufferSpan;
this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData);
return;
}
this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData);
}
private void DecodeYCbCrData(Buffer2D<TPixel> pixels, int left, int top, int width, int height, ReadOnlySpan<byte> ycbcrData)
{
var color = default(TPixel);
int offset = 0;
int widthPadding = 0;

2
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
/// <returns> The tiff frame. </returns>
/// <returns>The tiff frame.</returns>
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{

8
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -459,6 +459,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null)
{
// Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data.
options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
options.ColorType = TiffColorType.Rgb;
}
break;
}

31
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerPixel)
{
case TiffBitsPerPixel.Bit1:
if (compression == TiffCompression.Ccitt1D || compression == TiffCompression.CcittGroup3Fax || compression == TiffCompression.CcittGroup4Fax)
if (IsOneBitCompression(compression))
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
@ -375,6 +375,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
this.CompressionType = DefaultCompression;
}
return;
}
@ -396,18 +403,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (this.CompressionType == TiffCompression.Ccitt1D ||
this.CompressionType == TiffCompression.CcittGroup3Fax ||
this.CompressionType == TiffCompression.CcittGroup4Fax)
if (IsOneBitCompression(this.CompressionType))
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None);
return;
}
else
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
case TiffPhotometricInterpretation.PaletteColor:
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
@ -428,5 +431,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.CompressionType = compression;
this.HorizontalPredictor = predictor;
}
public static bool IsOneBitCompression(TiffCompression? compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
}
}

2
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -18,8 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
private const float Scale32Bit = 1.0f / 0xFFFFFFFF;
public static Vector4 Vector4Default { get; } = new(0.0f, 0.0f, 0.0f, 0.0f);
public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0);
public static L16 L16Default { get; } = new(0);

92
src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs

@ -13,36 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{
internal static class ColorSpaceTransformUtils
{
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<byte> CollectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte();
private static readonly Vector128<byte> CollectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte();
private static readonly Vector256<byte> CollectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte();
private static readonly Vector256<byte> CollectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte();
private static readonly Vector128<byte> CollectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
private static readonly Vector128<byte> CollectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0);
private static readonly Vector128<byte> CollectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
private static readonly Vector128<byte> CollectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255);
private static readonly Vector128<byte> CollectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14);
private static readonly Vector256<byte> CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255);
private static readonly Vector256<byte> CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30);
private static readonly Vector256<byte> CollectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0);
private static readonly Vector256<byte> CollectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
private static readonly Vector256<byte> CollectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
#endif
public static void CollectColorBlueTransforms(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span<int> histo)
{
#if SUPPORTS_RUNTIME_INTRINSICS
@ -50,8 +20,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{
const int span = 16;
Span<ushort> values = stackalloc ushort[span];
var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue));
var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue));
Vector256<byte> collectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255);
Vector256<byte> collectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30);
Vector256<byte> collectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0);
Vector256<byte> collectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector256<byte> collectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector256<short> multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue));
Vector256<short> multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue));
for (int y = 0; y < tileHeight; y++)
{
Span<uint> srcSpan = bgra.Slice(y * stride);
@ -62,18 +37,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2);
Vector256<byte> input0 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte();
Vector256<byte> input1 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector256<byte> r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256);
Vector256<byte> r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256);
Vector256<byte> r0 = Avx2.Shuffle(input0, collectColorBlueTransformsShuffleLowMask256);
Vector256<byte> r1 = Avx2.Shuffle(input1, collectColorBlueTransformsShuffleHighMask256);
Vector256<byte> r = Avx2.Or(r0, r1);
Vector256<byte> gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256);
Vector256<byte> gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256);
Vector256<byte> gb0 = Avx2.And(input0, collectColorBlueTransformsGreenBlueMask256);
Vector256<byte> gb1 = Avx2.And(input1, collectColorBlueTransformsGreenBlueMask256);
Vector256<ushort> gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32());
Vector256<byte> g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256);
Vector256<byte> g = Avx2.And(gb.AsByte(), collectColorBlueTransformsGreenMask256);
Vector256<short> a = Avx2.MultiplyHigh(r.AsInt16(), multsr);
Vector256<short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg);
Vector256<byte> c = Avx2.Subtract(gb.AsByte(), b.AsByte());
Vector256<byte> d = Avx2.Subtract(c, a.AsByte());
Vector256<byte> e = Avx2.And(d, CollectColorBlueTransformsBlueMask256);
Vector256<byte> e = Avx2.And(d, collectColorBlueTransformsBlueMask256);
ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector256<ushort>>(ref outputRef) = e.AsUInt16();
@ -95,8 +70,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{
const int span = 8;
Span<ushort> values = stackalloc ushort[span];
var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue));
var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue));
Vector128<byte> collectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255);
Vector128<byte> collectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14);
Vector128<byte> collectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0);
Vector128<byte> collectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector128<byte> collectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector128<short> multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue));
Vector128<short> multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue));
for (int y = 0; y < tileHeight; y++)
{
Span<uint> srcSpan = bgra.Slice(y * stride);
@ -107,18 +87,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2);
Vector128<byte> input0 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte();
Vector128<byte> input1 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector128<byte> r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask);
Vector128<byte> r1 = Ssse3.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask);
Vector128<byte> r0 = Ssse3.Shuffle(input0, collectColorBlueTransformsShuffleLowMask);
Vector128<byte> r1 = Ssse3.Shuffle(input1, collectColorBlueTransformsShuffleHighMask);
Vector128<byte> r = Sse2.Or(r0, r1);
Vector128<byte> gb0 = Sse2.And(input0, CollectColorBlueTransformsGreenBlueMask);
Vector128<byte> gb1 = Sse2.And(input1, CollectColorBlueTransformsGreenBlueMask);
Vector128<byte> gb0 = Sse2.And(input0, collectColorBlueTransformsGreenBlueMask);
Vector128<byte> gb1 = Sse2.And(input1, collectColorBlueTransformsGreenBlueMask);
Vector128<ushort> gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32());
Vector128<byte> g = Sse2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask);
Vector128<byte> g = Sse2.And(gb.AsByte(), collectColorBlueTransformsGreenMask);
Vector128<short> a = Sse2.MultiplyHigh(r.AsInt16(), multsr);
Vector128<short> b = Sse2.MultiplyHigh(g.AsInt16(), multsg);
Vector128<byte> c = Sse2.Subtract(gb.AsByte(), b.AsByte());
Vector128<byte> d = Sse2.Subtract(c, a.AsByte());
Vector128<byte> e = Sse2.And(d, CollectColorBlueTransformsBlueMask);
Vector128<byte> e = Sse2.And(d, collectColorBlueTransformsBlueMask);
ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector128<ushort>>(ref outputRef) = e.AsUInt16();
@ -163,7 +143,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && tileWidth >= 16)
{
var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed));
Vector256<byte> collectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte();
Vector256<byte> collectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte();
Vector256<short> multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed));
const int span = 16;
Span<ushort> values = stackalloc ushort[span];
for (int y = 0; y < tileHeight; y++)
@ -176,15 +158,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2);
Vector256<byte> input0 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte();
Vector256<byte> input1 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector256<byte> g0 = Avx2.And(input0, CollectColorRedTransformsGreenMask256); // 0 0 | g 0
Vector256<byte> g1 = Avx2.And(input1, CollectColorRedTransformsGreenMask256);
Vector256<byte> g0 = Avx2.And(input0, collectColorRedTransformsGreenMask256); // 0 0 | g 0
Vector256<byte> g1 = Avx2.And(input1, collectColorRedTransformsGreenMask256);
Vector256<ushort> g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0
Vector256<int> a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r
Vector256<int> a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16);
Vector256<ushort> a = Avx2.PackUnsignedSaturate(a0, a1); // x r
Vector256<short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr
Vector256<byte> c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r'
Vector256<byte> d = Avx2.And(c, CollectColorRedTransformsAndMask256); // 0 r'
Vector256<byte> d = Avx2.And(c, collectColorRedTransformsAndMask256); // 0 r'
ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector256<ushort>>(ref outputRef) = d.AsUInt16();
@ -204,7 +186,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
else if (Sse41.IsSupported)
{
var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed));
Vector128<byte> collectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte();
Vector128<byte> collectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte();
Vector128<short> multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed));
const int span = 8;
Span<ushort> values = stackalloc ushort[span];
for (int y = 0; y < tileHeight; y++)
@ -217,15 +201,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2);
Vector128<byte> input0 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte();
Vector128<byte> input1 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector128<byte> g0 = Sse2.And(input0, CollectColorRedTransformsGreenMask); // 0 0 | g 0
Vector128<byte> g1 = Sse2.And(input1, CollectColorRedTransformsGreenMask);
Vector128<byte> g0 = Sse2.And(input0, collectColorRedTransformsGreenMask); // 0 0 | g 0
Vector128<byte> g1 = Sse2.And(input1, collectColorRedTransformsGreenMask);
Vector128<ushort> g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0
Vector128<int> a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r
Vector128<int> a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16);
Vector128<ushort> a = Sse41.PackUnsignedSaturate(a0, a1); // x r
Vector128<short> b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr
Vector128<byte> c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r'
Vector128<byte> d = Sse2.And(c, CollectColorRedTransformsAndMask); // 0 r'
Vector128<byte> d = Sse2.And(c, collectColorRedTransformsAndMask); // 0 r'
ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector128<ushort>>(ref outputRef) = d.AsUInt16();

96
src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs

@ -27,36 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private const double Log2Reciprocal = 1.44269504088896338700465094007086;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector256<byte> AddGreenToBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
private static readonly Vector128<byte> AddGreenToBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
private static readonly byte AddGreenToBlueAndRedShuffleMask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0);
private static readonly Vector256<byte> SubtractGreenFromBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
private static readonly Vector128<byte> SubtractGreenFromBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
private static readonly byte SubtractGreenFromBlueAndRedShuffleMask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0);
private static readonly Vector128<byte> TransformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
private static readonly Vector256<byte> TransformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
private static readonly Vector128<byte> TransformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
private static readonly Vector256<byte> TransformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
private static readonly byte TransformColorShuffleMask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0);
private static readonly Vector128<byte> TransformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
private static readonly Vector256<byte> TransformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
private static readonly byte TransformColorInverseShuffleMask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0);
#endif
/// <summary>
/// Returns the exact index where array1 and array2 are different. For an index
/// inferior or equal to bestLenMatch, the return value just has to be strictly
@ -129,13 +99,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> addGreenToBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 8; i += 8)
{
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector256<byte> input = Unsafe.As<uint, Vector256<uint>>(ref pos).AsByte();
Vector256<byte> in0g0g = Avx2.Shuffle(input, AddGreenToBlueAndRedMaskAvx2);
Vector256<byte> in0g0g = Avx2.Shuffle(input, addGreenToBlueAndRedMaskAvx2);
Vector256<byte> output = Avx2.Add(input, in0g0g);
Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32();
}
@ -147,13 +118,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
else if (Ssse3.IsSupported)
{
Vector128<byte> addGreenToBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
{
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<byte> in0g0g = Ssse3.Shuffle(input, AddGreenToBlueAndRedMaskSsse3);
Vector128<byte> in0g0g = Ssse3.Shuffle(input, addGreenToBlueAndRedMaskSsse3);
Vector128<byte> output = Sse2.Add(input, in0g0g);
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -169,11 +141,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<ushort> a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g
Vector128<ushort> b = Sse2.ShuffleLow(a, AddGreenToBlueAndRedShuffleMask);
Vector128<ushort> c = Sse2.ShuffleHigh(b, AddGreenToBlueAndRedShuffleMask); // 0g0g
Vector128<ushort> b = Sse2.ShuffleLow(a, mmShuffle_2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g
Vector128<byte> output = Sse2.Add(input.AsByte(), c.AsByte());
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -209,13 +183,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> subtractGreenFromBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 8; i += 8)
{
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector256<byte> input = Unsafe.As<uint, Vector256<uint>>(ref pos).AsByte();
Vector256<byte> in0g0g = Avx2.Shuffle(input, SubtractGreenFromBlueAndRedMaskAvx2);
Vector256<byte> in0g0g = Avx2.Shuffle(input, subtractGreenFromBlueAndRedMaskAvx2);
Vector256<byte> output = Avx2.Subtract(input, in0g0g);
Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32();
}
@ -227,13 +202,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
else if (Ssse3.IsSupported)
{
Vector128<byte> subtractGreenFromBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255);
int numPixels = pixelData.Length;
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
{
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<byte> in0g0g = Ssse3.Shuffle(input, SubtractGreenFromBlueAndRedMaskSsse3);
Vector128<byte> in0g0g = Ssse3.Shuffle(input, subtractGreenFromBlueAndRedMaskSsse3);
Vector128<byte> output = Sse2.Subtract(input, in0g0g);
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -249,11 +225,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint i;
for (i = 0; i <= numPixels - 4; i += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<ushort> a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g
Vector128<ushort> b = Sse2.ShuffleLow(a, SubtractGreenFromBlueAndRedShuffleMask);
Vector128<ushort> c = Sse2.ShuffleHigh(b, SubtractGreenFromBlueAndRedShuffleMask); // 0g0g
Vector128<ushort> b = Sse2.ShuffleLow(a, mmShuffle_2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g
Vector128<byte> output = Sse2.Subtract(input.AsByte(), c.AsByte());
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -400,23 +378,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && numPixels >= 8)
{
Vector256<byte> transformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector256<byte> transformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector256<int> multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= numPixels - 8; idx += 8)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos);
Vector256<byte> a = Avx2.And(input.AsByte(), TransformColorAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask);
Vector256<byte> a = Avx2.And(input.AsByte(), transformColorAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector256<short> e = Avx2.ShiftLeftLogical(input.AsInt16(), 8);
Vector256<short> f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
Vector256<int> g = Avx2.ShiftRightLogical(f.AsInt32(), 16);
Vector256<byte> h = Avx2.Add(g.AsByte(), d.AsByte());
Vector256<byte> i = Avx2.And(h, TransformColorRedBlueMask256);
Vector256<byte> i = Avx2.And(h, transformColorRedBlueMask256);
Vector256<byte> output = Avx2.Subtract(input.AsByte(), i);
Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32();
}
@ -428,22 +410,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
else if (Sse2.IsSupported)
{
Vector128<byte> transformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector128<byte> transformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0);
Vector128<int> multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= numPixels - 4; idx += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos);
Vector128<byte> a = Sse2.And(input.AsByte(), TransformColorAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask);
Vector128<byte> a = Sse2.And(input.AsByte(), transformColorAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<short> e = Sse2.ShiftLeftLogical(input.AsInt16(), 8);
Vector128<short> f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
Vector128<int> g = Sse2.ShiftRightLogical(f.AsInt32(), 16);
Vector128<byte> h = Sse2.Add(g.AsByte(), d.AsByte());
Vector128<byte> i = Sse2.And(h, TransformColorRedBlueMask);
Vector128<byte> i = Sse2.And(h, transformColorRedBlueMask);
Vector128<byte> output = Sse2.Subtract(input.AsByte(), i);
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
}
@ -488,16 +474,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && pixelData.Length >= 8)
{
Vector256<byte> transformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector256<int> multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= pixelData.Length - 8; idx += 8)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos);
Vector256<byte> a = Avx2.And(input.AsByte(), TransformColorInverseAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask);
Vector256<byte> a = Avx2.And(input.AsByte(), transformColorInverseAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector256<byte> e = Avx2.Add(input.AsByte(), d.AsByte());
Vector256<short> f = Avx2.ShiftLeftLogical(e.AsInt16(), 8);
@ -516,17 +505,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
else if (Sse2.IsSupported)
{
Vector128<byte> transformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255);
Vector128<int> multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0);
nint idx;
for (idx = 0; idx <= pixelData.Length - 4; idx += 4)
{
const byte mmShuffle_2200 = 0b_10_10_00_00;
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos);
Vector128<byte> a = Sse2.And(input.AsByte(), TransformColorInverseAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask);
Vector128<byte> a = Sse2.And(input.AsByte(), transformColorInverseAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<byte> e = Sse2.Add(input.AsByte(), d.AsByte());
Vector128<short> f = Sse2.ShiftLeftLogical(e.AsInt16(), 8);

147
src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs

@ -15,29 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
internal static class LossyUtils
{
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<byte> Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte();
private static readonly Vector128<byte> SignBit = Vector128.Create((byte)0x80);
private static readonly Vector128<sbyte> Three = Vector128.Create((byte)3).AsSByte();
private static readonly Vector128<short> FourShort = Vector128.Create((short)4);
private static readonly Vector128<sbyte> FourSByte = Vector128.Create((byte)4).AsSByte();
private static readonly Vector128<sbyte> Nine = Vector128.Create((short)0x0900).AsSByte();
private static readonly Vector128<sbyte> SixtyThree = Vector128.Create((short)63).AsSByte();
private static readonly Vector128<sbyte> SixtyFour = Vector128.Create((byte)64).AsSByte();
private static readonly Vector128<short> K1 = Vector128.Create((short)20091);
private static readonly Vector128<short> K2 = Vector128.Create((short)-30068);
#endif
// Note: method name in libwebp reference implementation is called VP8SSE16x16.
[MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8_Sse16X16(Span<byte> a, Span<byte> b)
@ -1025,16 +1002,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<short> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16());
Vector128<short> k1 = Vector128.Create((short)20091);
Vector128<short> k2 = Vector128.Create((short)-30068);
// c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1);
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3.AsInt16(), c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2);
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4);
@ -1050,20 +1030,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> dc = Sse2.Add(t0.AsInt16(), FourShort);
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4));
a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16());
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1);
c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1);
c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
c4 = Sse2.Subtract(c1, c2);
c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2);
d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2);
d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
d4 = Sse2.Add(d1, d2);
d = Sse2.Add(d3, d4);
@ -1146,16 +1126,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<short> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16());
Vector128<short> k1 = Vector128.Create((short)20091);
Vector128<short> k2 = Vector128.Create((short)-30068);
// c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1);
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3.AsInt16(), c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2);
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4);
@ -1171,20 +1154,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> dc = Sse2.Add(t0.AsInt16(), FourShort);
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4));
a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16());
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1);
c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1);
c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
c4 = Sse2.Subtract(c1, c2);
c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2);
d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2);
d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
d4 = Sse2.Add(d1, d2);
d = Sse2.Add(d3, d4);
@ -1810,6 +1793,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
#if SUPPORTS_RUNTIME_INTRINSICS
if (Ssse3.IsSupported)
{
Vector128<byte> mean16x4Mask = Vector128.Create((short)0x00ff).AsByte();
Vector128<byte> a0 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
Vector128<byte> a1 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16)));
Vector128<byte> a2 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16)));
@ -1818,10 +1803,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<short> b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8);
Vector128<short> b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8);
Vector128<short> b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8);
Vector128<byte> c0 = Sse2.And(a0, Mean16x4Mask); // lo byte
Vector128<byte> c1 = Sse2.And(a1, Mean16x4Mask);
Vector128<byte> c2 = Sse2.And(a2, Mean16x4Mask);
Vector128<byte> c3 = Sse2.And(a3, Mean16x4Mask);
Vector128<byte> c0 = Sse2.And(a0, mean16x4Mask); // lo byte
Vector128<byte> c1 = Sse2.And(a1, mean16x4Mask);
Vector128<byte> c2 = Sse2.And(a2, mean16x4Mask);
Vector128<byte> c3 = Sse2.And(a3, mean16x4Mask);
Vector128<int> d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32());
Vector128<int> d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32());
Vector128<int> d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32());
@ -1978,14 +1963,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Applies filter on 2 pixels (p0 and q0)
private static void DoFilter2Sse2(ref Vector128<byte> p1, ref Vector128<byte> p0, ref Vector128<byte> q0, ref Vector128<byte> q1, int thresh)
{
Vector128<byte> signBit = Vector128.Create((byte)0x80);
// Convert p1/q1 to byte (for GetBaseDelta).
Vector128<byte> p1s = Sse2.Xor(p1, SignBit);
Vector128<byte> q1s = Sse2.Xor(q1, SignBit);
Vector128<byte> p1s = Sse2.Xor(p1, signBit);
Vector128<byte> q1s = Sse2.Xor(q1, signBit);
Vector128<byte> mask = NeedsFilter(p1, p0, q0, q1, thresh);
// Flip sign.
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, signBit);
Vector128<byte> a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte();
@ -1995,8 +1982,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
DoSimpleFilterSse2(ref p0, ref q0, a);
// Flip sign.
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, signBit);
}
// Applies filter on 4 pixels (p1, p0, q0 and q1)
@ -2005,11 +1992,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Compute hev mask.
Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh);
Vector128<byte> signBit = Vector128.Create((byte)0x80);
// Convert to signed values.
p1 = Sse2.Xor(p1, SignBit);
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
q1 = Sse2.Xor(q1, SignBit);
p1 = Sse2.Xor(p1, signBit);
p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, signBit);
q1 = Sse2.Xor(q1, signBit);
Vector128<sbyte> t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1
t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(p1 - q1)
@ -2019,25 +2008,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0)
t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about.
t2 = Sse2.AddSaturate(t1, Three); // 3 * (q0 - p0) + hev(p1 - q1) + 3
Vector128<sbyte> t3 = Sse2.AddSaturate(t1, FourSByte); // 3 * (q0 - p0) + hev(p1 - q1) + 4
t2 = Sse2.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3
Vector128<sbyte> t3 = Sse2.AddSaturate(t1, Vector128.Create((byte)4).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 4
t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3
t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3
p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2
q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, signBit);
// This is equivalent to signed (a + 1) >> 1 calculation.
t2 = Sse2.Add(t3, SignBit.AsSByte());
t2 = Sse2.Add(t3, signBit.AsSByte());
t3 = Sse2.Average(t2.AsByte(), Vector128<byte>.Zero).AsSByte();
t3 = Sse2.Subtract(t3, SixtyFour);
t3 = Sse2.Subtract(t3, Vector128.Create((sbyte)64));
t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev
q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3
p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3
p1 = Sse2.Xor(p1.AsByte(), SignBit);
q1 = Sse2.Xor(q1.AsByte(), SignBit);
p1 = Sse2.Xor(p1.AsByte(), signBit);
q1 = Sse2.Xor(q1.AsByte(), signBit);
}
// Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2)
@ -2047,12 +2036,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh);
// Convert to signed values.
p1 = Sse2.Xor(p1, SignBit);
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
q1 = Sse2.Xor(q1, SignBit);
p2 = Sse2.Xor(p2, SignBit);
q2 = Sse2.Xor(q2, SignBit);
Vector128<byte> signBit = Vector128.Create((byte)0x80);
p1 = Sse2.Xor(p1, signBit);
p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, signBit);
q1 = Sse2.Xor(q1, signBit);
p2 = Sse2.Xor(p2, signBit);
q2 = Sse2.Xor(q2, signBit);
Vector128<sbyte> a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte());
@ -2067,11 +2057,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<byte> flow = Sse2.UnpackLow(Vector128<byte>.Zero, f);
Vector128<byte> fhigh = Sse2.UnpackHigh(Vector128<byte>.Zero, f);
Vector128<short> f9Low = Sse2.MultiplyHigh(flow.AsInt16(), Nine.AsInt16()); // Filter (lo) * 9
Vector128<short> f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9
Vector128<short> nine = Vector128.Create((short)0x0900);
Vector128<short> f9Low = Sse2.MultiplyHigh(flow.AsInt16(), nine); // Filter (lo) * 9
Vector128<short> f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), nine); // Filter (hi) * 9
Vector128<short> a2Low = Sse2.Add(f9Low, SixtyThree.AsInt16()); // Filter * 9 + 63
Vector128<short> a2High = Sse2.Add(f9High, SixtyThree.AsInt16()); // Filter * 9 + 63
Vector128<short> sixtyThree = Vector128.Create((short)63);
Vector128<short> a2Low = Sse2.Add(f9Low, sixtyThree); // Filter * 9 + 63
Vector128<short> a2High = Sse2.Add(f9High, sixtyThree); // Filter * 9 + 63
Vector128<short> a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63
Vector128<short> a1High = Sse2.Add(a2High, f9High); // // Filter * 18 + 63
@ -2086,8 +2078,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private static void DoSimpleFilterSse2(ref Vector128<byte> p0, ref Vector128<byte> q0, Vector128<byte> fl)
{
Vector128<sbyte> v3 = Sse2.AddSaturate(fl.AsSByte(), Three);
Vector128<sbyte> v4 = Sse2.AddSaturate(fl.AsSByte(), FourSByte);
Vector128<sbyte> v3 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte());
Vector128<sbyte> v4 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte());
v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3
v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3
@ -2353,13 +2345,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip).
private static void Update2Pixels(ref Vector128<byte> pi, ref Vector128<byte> qi, Vector128<short> a0Low, Vector128<short> a0High)
{
Vector128<byte> signBit = Vector128.Create((byte)0x80);
Vector128<short> a1Low = Sse2.ShiftRightArithmetic(a0Low, 7);
Vector128<short> a1High = Sse2.ShiftRightArithmetic(a0High, 7);
Vector128<sbyte> delta = Sse2.PackSignedSaturate(a1Low, a1High);
pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte();
qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte();
pi = Sse2.Xor(pi, SignBit.AsByte());
qi = Sse2.Xor(qi, SignBit.AsByte());
pi = Sse2.Xor(pi, signBit.AsByte());
qi = Sse2.Xor(qi, signBit.AsByte());
}
[MethodImpl(InliningOptions.ShortMethod)]

37
src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs

@ -20,24 +20,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int MaxLevel = 2047;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<short> MaxCoeff2047 = Vector128.Create((short)MaxLevel);
private static readonly Vector256<short> MaxCoeff2047Vec256 = Vector256.Create((short)MaxLevel);
private static readonly Vector256<byte> Cst256 = Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15);
private static readonly Vector256<byte> Cst78 = Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255);
private static readonly Vector128<byte> CstLo = Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13);
private static readonly Vector128<byte> Cst7 = Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255);
private static readonly Vector128<byte> CstHi = Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15);
private static readonly Vector128<byte> Cst8 = Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255);
#endif
// Diffusion weights. We under-correct a bit (15/16th of the error is actually
// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0.
private const int C1 = 7; // fraction of error sent to the 4x4 block below
@ -574,7 +556,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector256<short> out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32());
// if (coeff > 2047) coeff = 2047
out0 = Avx2.Min(out0, MaxCoeff2047Vec256);
out0 = Avx2.Min(out0, Vector256.Create((short)MaxLevel));
// Put the sign back.
out0 = Avx2.Sign(out0, input0);
@ -585,8 +567,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Unsafe.As<short, Vector256<short>>(ref inputRef) = input0;
// zigzag the output before storing it.
Vector256<byte> tmp256 = Avx2.Shuffle(out0.AsByte(), Cst256);
Vector256<byte> tmp78 = Avx2.Shuffle(out0.AsByte(), Cst78);
Vector256<byte> tmp256 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); // Cst256
Vector256<byte> tmp78 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // Cst78
// Reverse the order of the 16-byte lanes.
Vector256<byte> tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1);
@ -654,8 +636,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<short> out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32());
// if (coeff > 2047) coeff = 2047
out0 = Sse2.Min(out0, MaxCoeff2047);
out8 = Sse2.Min(out8, MaxCoeff2047);
Vector128<short> maxCoeff2047 = Vector128.Create((short)MaxLevel);
out0 = Sse2.Min(out0, maxCoeff2047);
out8 = Sse2.Min(out8, maxCoeff2047);
// Put the sign back.
out0 = Ssse3.Sign(out0, input0);
@ -676,10 +659,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// There's only two misplaced entries ([8] and [7]) that are crossing the
// reg's boundaries.
// We use pshufb instead of pshuflo/pshufhi.
Vector128<byte> tmpLo = Ssse3.Shuffle(out0.AsByte(), CstLo);
Vector128<byte> tmp7 = Ssse3.Shuffle(out0.AsByte(), Cst7); // extract #7
Vector128<byte> tmpHi = Ssse3.Shuffle(out8.AsByte(), CstHi);
Vector128<byte> tmp8 = Ssse3.Shuffle(out8.AsByte(), Cst8); // extract #8
Vector128<byte> tmpLo = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13));
Vector128<byte> tmp7 = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255)); // extract #7
Vector128<byte> tmpHi = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15));
Vector128<byte> tmp8 = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // extract #8
Vector128<byte> outZ0 = Sse2.Or(tmpLo, tmp8);
Vector128<byte> outZ8 = Sse2.Or(tmpHi, tmp7);

105
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int KC2 = 35468;
private static readonly byte[] Clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255]
private static readonly byte[] Clip1 = GetClip1(); // clips [-255,510] to [0,255]
private const int I16DC16 = 0 * 16 * WebpConstants.Bps;
@ -65,48 +65,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 };
#if SUPPORTS_RUNTIME_INTRINSICS
#pragma warning disable SA1310 // Field names should not contain underscore
private static readonly Vector128<short> K1 = Vector128.Create((short)20091).AsInt16();
private static readonly Vector128<short> K2 = Vector128.Create((short)-30068).AsInt16();
private static readonly Vector128<short> Four = Vector128.Create((short)4);
private static readonly Vector128<short> Seven = Vector128.Create((short)7);
private static readonly Vector128<short> K88p = Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16();
private static readonly Vector128<short> K88m = Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16();
private static readonly Vector128<short> K5352_2217p = Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16();
private static readonly Vector128<short> K5352_2217m = Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16();
private static readonly Vector128<int> K937 = Vector128.Create(937);
private static readonly Vector128<int> K1812 = Vector128.Create(1812);
private static readonly Vector128<short> K5352_2217 = Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16();
private static readonly Vector128<short> K2217_5352 = Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16();
private static readonly Vector128<int> K12000PlusOne = Vector128.Create(12000 + (1 << 16));
private static readonly Vector128<int> K51000 = Vector128.Create(51000);
private static readonly byte MmShuffle2301 = SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1);
private static readonly byte MmShuffle1032 = SimdUtils.Shuffle.MmShuffle(1, 0, 3, 2);
#pragma warning restore SA1310 // Field names should not contain underscore
#endif
static Vp8Encoding()
private static byte[] GetClip1()
{
byte[] clip1 = new byte[255 + 510 + 1];
for (int i = -255; i <= 255 + 255; i++)
{
Clip1[255 + i] = Clip8b(i);
clip1[255 + i] = Clip8b(i);
}
return clip1;
}
// Transforms (Paragraph 14.4)
@ -347,16 +315,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<short> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16());
Vector128<short> k1 = Vector128.Create((short)20091).AsInt16();
Vector128<short> k2 = Vector128.Create((short)-30068).AsInt16();
// c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1);
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3, c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2);
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4);
@ -370,20 +341,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private static void InverseTransformHorizontalPass(Vector128<long> t0, Vector128<long> t2, Vector128<long> t1, Vector128<long> t3, out Vector128<short> shifted0, out Vector128<short> shifted1, out Vector128<short> shifted2, out Vector128<short> shifted3)
{
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Four);
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4));
Vector128<short> a = Sse2.Add(dc, t2.AsInt16());
Vector128<short> b = Sse2.Subtract(dc, t2.AsInt16());
Vector128<short> k1 = Vector128.Create((short)20091).AsInt16();
Vector128<short> k2 = Vector128.Create((short)-30068).AsInt16();
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
Vector128<short> c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2);
Vector128<short> c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1);
Vector128<short> c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
Vector128<short> d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1);
Vector128<short> d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2);
Vector128<short> d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4);
@ -561,8 +535,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
// *in01 = 00 01 10 11 02 03 12 13
// *in23 = 20 21 30 31 22 23 32 33
Vector128<short> shuf01_p = Sse2.ShuffleHigh(row01, MmShuffle2301);
Vector128<short> shuf32_p = Sse2.ShuffleHigh(row23, MmShuffle2301);
const byte mmShuffle_2301 = 0b_10_11_00_01;
Vector128<short> shuf01_p = Sse2.ShuffleHigh(row01, mmShuffle_2301);
Vector128<short> shuf32_p = Sse2.ShuffleHigh(row23, mmShuffle_2301);
// 00 01 10 11 03 02 13 12
// 20 21 30 31 23 22 33 32
@ -576,12 +551,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ]
// [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ]
Vector128<int> tmp0 = Sse2.MultiplyAddAdjacent(a01, K88p); // [ (a0 + a1) << 3, ... ]
Vector128<int> tmp2 = Sse2.MultiplyAddAdjacent(a01, K88m); // [ (a0 - a1) << 3, ... ]
Vector128<int> tmp11 = Sse2.MultiplyAddAdjacent(a32, K5352_2217p);
Vector128<int> tmp31 = Sse2.MultiplyAddAdjacent(a32, K5352_2217m);
Vector128<int> tmp12 = Sse2.Add(tmp11, K1812);
Vector128<int> tmp32 = Sse2.Add(tmp31, K937);
// [ (a0 + a1) << 3, ... ]
Vector128<int> tmp0 = Sse2.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16()); // K88p
// [ (a0 - a1) << 3, ... ]
Vector128<int> tmp2 = Sse2.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16()); // K88m
Vector128<int> tmp11 = Sse2.MultiplyAddAdjacent(a32, Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16()); // K5352_2217p
Vector128<int> tmp31 = Sse2.MultiplyAddAdjacent(a32, Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16()); // K5352_2217m
Vector128<int> tmp12 = Sse2.Add(tmp11, Vector128.Create(1812));
Vector128<int> tmp32 = Sse2.Add(tmp31, Vector128.Create(937));
Vector128<int> tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9);
Vector128<int> tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9);
Vector128<short> s03 = Sse2.PackSignedSaturate(tmp0, tmp2);
@ -590,7 +569,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<short> shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3
Vector128<int> v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32());
out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32());
out32 = Sse2.Shuffle(v23, MmShuffle1032);
const byte mmShuffle_1032 = 0b_01_00_11_10;
out32 = Sse2.Shuffle(v23, mmShuffle_1032);
}
public static void FTransformPass2SSE2(Vector128<int> v01, Vector128<int> v32, Span<short> output)
@ -602,10 +583,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<long> a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64());
Vector128<short> b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16());
Vector128<int> c1 = Sse2.MultiplyAddAdjacent(b23, K5352_2217);
Vector128<int> c3 = Sse2.MultiplyAddAdjacent(b23, K2217_5352);
Vector128<int> d1 = Sse2.Add(c1, K12000PlusOne);
Vector128<int> d3 = Sse2.Add(c3, K51000);
Vector128<int> c1 = Sse2.MultiplyAddAdjacent(b23, Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16()); // K5352_2217
Vector128<int> c3 = Sse2.MultiplyAddAdjacent(b23, Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16()); // K2217_5352
Vector128<int> d1 = Sse2.Add(c1, Vector128.Create(12000 + (1 << 16))); // K12000PlusOne
Vector128<int> d3 = Sse2.Add(c3, Vector128.Create(51000));
Vector128<int> e1 = Sse2.ShiftRightArithmetic(d1, 16);
Vector128<int> e3 = Sse2.ShiftRightArithmetic(d3, 16);
@ -623,7 +604,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// a0 = v0 + v3
// a1 = v1 + v2
Vector128<short> a01 = Sse2.Add(v01.AsInt16(), v32.AsInt16());
Vector128<short> a01Plus7 = Sse2.Add(a01.AsInt16(), Seven);
Vector128<short> a01Plus7 = Sse2.Add(a01.AsInt16(), Vector128.Create((short)7));
Vector128<short> a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16();
Vector128<short> c0 = Sse2.Add(a01Plus7, a11);
Vector128<short> c2 = Sse2.Subtract(a01Plus7, a11);

6
src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs

@ -24,10 +24,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary>
private const int MaxCoeffThresh = 31;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector256<short> MaxCoeffThreshVec = Vector256.Create((short)MaxCoeffThresh);
#endif
private int maxValue;
private int lastNonZero;
@ -73,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector256<short> v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3);
// bin = min(v, MAX_COEFF_THRESH)
Vector256<short> min0 = Avx2.Min(v0, MaxCoeffThreshVec);
Vector256<short> min0 = Avx2.Min(v0, Vector256.Create((short)MaxCoeffThresh));
// Store.
Unsafe.As<short, Vector256<short>>(ref outputRef) = min0;

10
src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs

@ -16,12 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary>
internal class Vp8Residual
{
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector256<byte> Cst2 = Vector256.Create((byte)2);
private static readonly Vector256<byte> Cst67 = Vector256.Create((byte)67);
#endif
private readonly byte[] scratch = new byte[32];
private readonly ushort[] scratchUShort = new ushort[16];
@ -182,8 +176,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector256<short> d0 = Avx2.Subtract(Vector256<short>.Zero, c0);
Vector256<short> e0 = Avx2.Max(c0, d0); // abs(v), 16b
Vector256<sbyte> f = Avx2.PackSignedSaturate(e0, e0);
Vector256<byte> g = Avx2.Min(f.AsByte(), Cst2);
Vector256<byte> h = Avx2.Min(f.AsByte(), Cst67); // clampLevel in [0..67]
Vector256<byte> g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2));
Vector256<byte> h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67]
ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs);
ref byte levelsRef = ref MemoryMarshal.GetReference(levels);

89
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -23,49 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int YuvHalf = 1 << (YuvFix - 1);
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<byte> One = Vector128.Create((byte)1);
// These constants are 14b fixed-point version of ITU-R BT.601 constants.
// R = (19077 * y + 26149 * v - 14234) >> 6
// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6
// B = (19077 * y + 33050 * u - 17685) >> 6
private static readonly Vector128<byte> K19077 = Vector128.Create((short)19077).AsByte();
private static readonly Vector128<byte> K26149 = Vector128.Create((short)26149).AsByte();
private static readonly Vector128<byte> K14234 = Vector128.Create((short)14234).AsByte();
// 33050 doesn't fit in a signed short: only use this with unsigned arithmetic
private static readonly Vector128<byte> K33050 = Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129);
private static readonly Vector128<byte> K17685 = Vector128.Create((short)17685).AsByte();
private static readonly Vector128<byte> K6419 = Vector128.Create((short)6419).AsByte();
private static readonly Vector128<byte> K13320 = Vector128.Create((short)13320).AsByte();
private static readonly Vector128<byte> K8708 = Vector128.Create((short)8708).AsByte();
private static readonly Vector128<byte> PlanarTo24Shuffle0 = Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5);
private static readonly Vector128<byte> PlanarTo24Shuffle1 = Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255);
private static readonly Vector128<byte> PlanarTo24Shuffle2 = Vector128.Create(255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255, 255);
private static readonly Vector128<byte> PlanarTo24Shuffle3 = Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255);
private static readonly Vector128<byte> PlanarTo24Shuffle4 = Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10);
private static readonly Vector128<byte> PlanarTo24Shuffle5 = Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255);
private static readonly Vector128<byte> PlanarTo24Shuffle6 = Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255);
private static readonly Vector128<byte> PlanarTo24Shuffle7 = Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255);
private static readonly Vector128<byte> PlanarTo24Shuffle8 = Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15);
#endif
// UpSample from YUV to RGB.
// Given samples laid out in a square as:
// [a b]
@ -250,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<byte> t1 = Sse2.Or(ad, bc); // (a^d) | (b^c)
Vector128<byte> t2 = Sse2.Or(t1, st); // (a^d) | (b^c) | (s^t)
Vector128<byte> t3 = Sse2.And(t2, One); // (a^d) | (b^c) | (s^t) & 1
Vector128<byte> t3 = Sse2.And(t2, Vector128.Create((byte)1)); // (a^d) | (b^c) | (s^t) & 1
Vector128<byte> t4 = Sse2.Average(s, t);
Vector128<byte> k = Sse2.Subtract(t4, t3); // k = (a + b + c + d) / 4
@ -289,7 +246,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector128<byte> tmp1 = Sse2.And(ij, st); // (ij) & (s^t)
Vector128<byte> tmp2 = Sse2.Xor(k, input); // (k^in)
Vector128<byte> tmp3 = Sse2.Or(tmp1, tmp2); // ((ij) & (s^t)) | (k^in)
Vector128<byte> tmp4 = Sse2.And(tmp3, One); // & 1 -> lsb_correction
Vector128<byte> tmp4 = Sse2.And(tmp3, Vector128.Create((byte)1)); // & 1 -> lsb_correction
return Sse2.Subtract(tmp0, tmp4); // (k + in + 1) / 2 - lsb_correction
}
@ -668,9 +625,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ChannelMixing(
input0,
input1,
PlanarTo24Shuffle0,
PlanarTo24Shuffle1,
PlanarTo24Shuffle2,
Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5), // PlanarTo24Shuffle0
Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255), // PlanarTo24Shuffle1
Vector128.Create(255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255, 255), // PlanarTo24Shuffle2
out Vector128<byte> r0,
out Vector128<byte> r1,
out Vector128<byte> r2,
@ -683,9 +640,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ChannelMixing(
input2,
input3,
PlanarTo24Shuffle3,
PlanarTo24Shuffle4,
PlanarTo24Shuffle5,
Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255), // PlanarTo24Shuffle3
Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10), // PlanarTo24Shuffle4
Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255), // PlanarTo24Shuffle5
out Vector128<byte> g0,
out Vector128<byte> g1,
out Vector128<byte> g2,
@ -697,9 +654,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ChannelMixing(
input4,
input5,
PlanarTo24Shuffle6,
PlanarTo24Shuffle7,
PlanarTo24Shuffle8,
Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255), // PlanarTo24Shuffle6
Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255), // PlanarTo24Shuffle7
Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15), // PlanarTo24Shuffle8
out Vector128<byte> b0,
out Vector128<byte> b1,
out Vector128<byte> b2,
@ -757,21 +714,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
u0 = Sse2.UnpackLow(Vector128<byte>.Zero, u0);
v0 = Sse2.UnpackLow(Vector128<byte>.Zero, v0);
Vector128<ushort> y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16());
Vector128<ushort> r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16());
Vector128<ushort> g0 = Sse2.MultiplyHigh(u0.AsUInt16(), K6419.AsUInt16());
Vector128<ushort> g1 = Sse2.MultiplyHigh(v0.AsUInt16(), K13320.AsUInt16());
// These constants are 14b fixed-point version of ITU-R BT.601 constants.
// R = (19077 * y + 26149 * v - 14234) >> 6
// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6
// B = (19077 * y + 33050 * u - 17685) >> 6
Vector128<ushort> k19077 = Vector128.Create((ushort)19077);
Vector128<ushort> k26149 = Vector128.Create((ushort)26149);
Vector128<ushort> k14234 = Vector128.Create((ushort)14234);
Vector128<ushort> y1 = Sse2.MultiplyHigh(y0.AsUInt16(), k19077);
Vector128<ushort> r0 = Sse2.MultiplyHigh(v0.AsUInt16(), k26149);
Vector128<ushort> g0 = Sse2.MultiplyHigh(u0.AsUInt16(), Vector128.Create((ushort)6419));
Vector128<ushort> g1 = Sse2.MultiplyHigh(v0.AsUInt16(), Vector128.Create((ushort)13320));
Vector128<ushort> r1 = Sse2.Subtract(y1.AsUInt16(), K14234.AsUInt16());
Vector128<ushort> r1 = Sse2.Subtract(y1.AsUInt16(), k14234);
Vector128<ushort> r2 = Sse2.Add(r1, r0);
Vector128<ushort> g2 = Sse2.Add(y1.AsUInt16(), K8708.AsUInt16());
Vector128<ushort> g2 = Sse2.Add(y1.AsUInt16(), Vector128.Create((ushort)8708));
Vector128<ushort> g3 = Sse2.Add(g0, g1);
Vector128<ushort> g4 = Sse2.Subtract(g2, g3);
Vector128<ushort> b0 = Sse2.MultiplyHigh(u0.AsUInt16(), K33050.AsUInt16());
Vector128<ushort> b0 = Sse2.MultiplyHigh(u0.AsUInt16(), Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129).AsUInt16());
Vector128<ushort> b1 = Sse2.AddSaturate(b0, y1);
Vector128<ushort> b2 = Sse2.SubtractSaturate(b1, K17685.AsUInt16());
Vector128<ushort> b2 = Sse2.SubtractSaturate(b1, Vector128.Create((ushort)17685));
// Use logical shift for B2, which can be larger than 32767.
r = Sse2.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815]

43
src/ImageSharp/Formats/Webp/WebpCommonUtils.cs

@ -16,16 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
internal static class WebpCommonUtils
{
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector256<byte> AlphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
private static readonly Vector256<byte> All0x80Vector256 = Vector256.Create((byte)0x80).AsByte();
private static readonly Vector128<byte> AlphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
private static readonly Vector128<byte> All0x80 = Vector128.Create((byte)0x80).AsByte();
#endif
/// <summary>
/// Checks if the pixel row is not opaque.
/// </summary>
@ -41,20 +31,23 @@ namespace SixLabors.ImageSharp.Formats.Webp
int length = (row.Length * 4) - 3;
fixed (byte* src = rowBytes)
{
Vector256<byte> alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
Vector256<byte> all0x80Vector256 = Vector256.Create((byte)0x80).AsByte();
for (; i + 128 <= length; i += 128)
{
Vector256<byte> a0 = Avx.LoadVector256(src + i).AsByte();
Vector256<byte> a1 = Avx.LoadVector256(src + i + 32).AsByte();
Vector256<byte> a2 = Avx.LoadVector256(src + i + 64).AsByte();
Vector256<byte> a3 = Avx.LoadVector256(src + i + 96).AsByte();
Vector256<int> b0 = Avx2.And(a0, AlphaMaskVector256).AsInt32();
Vector256<int> b1 = Avx2.And(a1, AlphaMaskVector256).AsInt32();
Vector256<int> b2 = Avx2.And(a2, AlphaMaskVector256).AsInt32();
Vector256<int> b3 = Avx2.And(a3, AlphaMaskVector256).AsInt32();
Vector256<int> b0 = Avx2.And(a0, alphaMaskVector256).AsInt32();
Vector256<int> b1 = Avx2.And(a1, alphaMaskVector256).AsInt32();
Vector256<int> b2 = Avx2.And(a2, alphaMaskVector256).AsInt32();
Vector256<int> b3 = Avx2.And(a3, alphaMaskVector256).AsInt32();
Vector256<short> c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16();
Vector256<short> c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16();
Vector256<byte> d = Avx2.PackSignedSaturate(c0, c1).AsByte();
Vector256<byte> bits = Avx2.CompareEqual(d, All0x80Vector256);
Vector256<byte> bits = Avx2.CompareEqual(d, all0x80Vector256);
int mask = Avx2.MoveMask(bits);
if (mask != -1)
{
@ -137,18 +130,20 @@ namespace SixLabors.ImageSharp.Formats.Webp
#if SUPPORTS_RUNTIME_INTRINSICS
private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i)
{
Vector128<byte> alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
Vector128<byte> a0 = Sse2.LoadVector128(src + i).AsByte();
Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte();
Vector128<byte> a2 = Sse2.LoadVector128(src + i + 32).AsByte();
Vector128<byte> a3 = Sse2.LoadVector128(src + i + 48).AsByte();
Vector128<int> b0 = Sse2.And(a0, AlphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, AlphaMask).AsInt32();
Vector128<int> b2 = Sse2.And(a2, AlphaMask).AsInt32();
Vector128<int> b3 = Sse2.And(a3, AlphaMask).AsInt32();
Vector128<int> b0 = Sse2.And(a0, alphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, alphaMask).AsInt32();
Vector128<int> b2 = Sse2.And(a2, alphaMask).AsInt32();
Vector128<int> b3 = Sse2.And(a3, alphaMask).AsInt32();
Vector128<short> c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16();
Vector128<short> c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16();
Vector128<byte> d = Sse2.PackSignedSaturate(c0, c1).AsByte();
Vector128<byte> bits = Sse2.CompareEqual(d, All0x80);
Vector128<byte> bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte());
int mask = Sse2.MoveMask(bits);
if (mask != 0xFFFF)
{
@ -160,13 +155,15 @@ namespace SixLabors.ImageSharp.Formats.Webp
private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i)
{
Vector128<byte> alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
Vector128<byte> a0 = Sse2.LoadVector128(src + i).AsByte();
Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte();
Vector128<int> b0 = Sse2.And(a0, AlphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, AlphaMask).AsInt32();
Vector128<int> b0 = Sse2.And(a0, alphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, alphaMask).AsInt32();
Vector128<short> c = Sse2.PackSignedSaturate(b0, b1).AsInt16();
Vector128<byte> d = Sse2.PackSignedSaturate(c, c).AsByte();
Vector128<byte> bits = Sse2.CompareEqual(d, All0x80);
Vector128<byte> bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte());
int mask = Sse2.MoveMask(bits);
if (mask != 0xFFFF)
{

2
tests/Directory.Build.targets

@ -21,7 +21,7 @@
<PackageReference Update="BenchmarkDotNet" Version="0.13.0" />
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" Condition="'$(IsWindows)'=='true'" />
<PackageReference Update="Colourful" Version="3.0.0" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="8.0.1" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="11.1.2" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Moq" Version="4.14.6" />

1
tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs

@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Params(
TestImages.Tiff.CcittFax3AllTermCodes,
TestImages.Tiff.Fax4Compressed2,
TestImages.Tiff.HuffmanRleAllMakeupCodes,
TestImages.Tiff.Calliphora_GrayscaleUncompressed,
TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor,

39
tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
@ -9,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
@ -17,11 +19,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeTiff
{
private System.Drawing.Image drawing;
private Stream stream;
private SDImage drawing;
private Image<Rgba32> core;
private Configuration configuration;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.Calliphora_RgbUncompressed)]
@ -29,9 +30,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Params(
TiffCompression.None,
TiffCompression.Deflate,
// System.Drawing does not support Deflate or PackBits
// TiffCompression.Deflate,
// TiffCompression.PackBits,
TiffCompression.Lzw,
TiffCompression.PackBits,
TiffCompression.CcittGroup3Fax,
TiffCompression.Ccitt1D)]
public TiffCompression Compression { get; set; }
@ -39,11 +42,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[GlobalSetup]
public void ReadImages()
{
if (this.core == null)
if (this.stream == null)
{
this.configuration = new Configuration();
this.core = Image.Load<Rgba32>(this.configuration, this.TestImageFullPath);
this.drawing = System.Drawing.Image.FromFile(this.TestImageFullPath);
this.stream = File.OpenRead(this.TestImageFullPath);
this.core = Image.Load<Rgba32>(this.stream);
this.stream.Position = 0;
this.drawing = SDImage.FromStream(this.stream);
}
}
@ -70,7 +74,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tiff")]
public void TiffCore()
{
TiffPhotometricInterpretation photometricInterpretation = TiffPhotometricInterpretation.Rgb;
TiffPhotometricInterpretation photometricInterpretation =
IsOneBitCompression(this.Compression) ?
TiffPhotometricInterpretation.WhiteIsZero :
TiffPhotometricInterpretation.Rgb;
var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using var memoryStream = new MemoryStream();
@ -109,8 +116,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
return EncoderValue.CompressionLZW;
default:
throw new System.NotSupportedException(compression.ToString());
throw new NotSupportedException(compression.ToString());
}
}
public static bool IsOneBitCompression(TiffCompression compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
}
}

8
tests/ImageSharp.Benchmarks/Config.cs

@ -32,17 +32,13 @@ namespace SixLabors.ImageSharp.Benchmarks
public class MultiFramework : Config
{
public MultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472),
Job.Default.WithRuntime(CoreRuntime.Core31),
Job.Default.WithRuntime(CoreRuntime.Core50).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
Job.Default.WithRuntime(CoreRuntime.Core60).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
}
public class ShortMultiFramework : Config
{
public ShortMultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core50).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
Job.Default.WithRuntime(CoreRuntime.Core60).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
}
public class ShortCore31 : Config

26
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -218,6 +218,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
// https://github.com/SixLabors/ImageSharp/issues/2133
[Theory]
[WithFile(TestImages.Jpeg.Issues.Issue2133_DeduceColorSpace, PixelTypes.Rgba32)]
public void Issue2133_DeduceColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
// https://github.com/SixLabors/ImageSharp/issues/2133
[Theory]
[WithFile(TestImages.Jpeg.Issues.Issue2136_ScanMarkerExtraneousBytes, PixelTypes.Rgba32)]
public void Issue2136_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
// DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\"

19
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs

@ -13,14 +13,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[Trait("Format", "Tiff")]
public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase
{
private static readonly Rgba32 Gray000 = new Rgba32(0, 0, 0, 255);
private static readonly Rgba32 Gray128 = new Rgba32(128, 128, 128, 255);
private static readonly Rgba32 Gray255 = new Rgba32(255, 255, 255, 255);
private static readonly Rgba32 Gray0 = new Rgba32(0, 0, 0, 255);
private static readonly Rgba32 Gray8 = new Rgba32(136, 136, 136, 255);
private static readonly Rgba32 GrayF = new Rgba32(255, 255, 255, 255);
private static readonly Rgba32 Bit0 = new Rgba32(0, 0, 0, 255);
private static readonly Rgba32 Bit1 = new Rgba32(255, 255, 255, 255);
private static readonly Rgba32 Gray000 = new(0, 0, 0, 255);
private static readonly Rgba32 Gray128 = new(128, 128, 128, 255);
private static readonly Rgba32 Gray255 = new(255, 255, 255, 255);
private static readonly Rgba32 Gray0 = new(0, 0, 0, 255);
private static readonly Rgba32 Gray8 = new(136, 136, 136, 255);
private static readonly Rgba32 GrayF = new(255, 255, 255, 255);
private static readonly Rgba32 Bit0 = new(0, 0, 0, 255);
private static readonly Rgba32 Bit1 = new(255, 255, 255, 255);
private static readonly byte[] BilevelBytes4X4 =
{
@ -30,8 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
0b10010000
};
private static readonly Rgba32[][] BilevelResult4X4 = new[]
{
private static readonly Rgba32[][] BilevelResult4X4 = {
new[] { Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, Bit1, Bit1, Bit1 },

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

@ -303,8 +303,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush1v1, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
@ -317,6 +315,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
// converting the pixel data from Magick.NET to our format with YCbCr?
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
[Theory]
@ -612,6 +611,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(Fax4Compressed, PixelTypes.Rgba32)]
[WithFile(Fax4Compressed2, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedMinIsBlack, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)]
[WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Fax4Compressed<TPixel>(TestImageProvider<TPixel> provider)
@ -642,10 +643,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(RgbJpegCompressed, PixelTypes.Rgba32)]
[WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)]
[WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)]
[WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)]
[WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)]
[WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)]
[WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)]
[WithFile(Issues2123, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_JpegCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, useExactComparer: false);
@ -661,6 +665,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
});
// https://github.com/SixLabors/ImageSharp/issues/2149
[Theory]
[WithFile(Issues2149, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Fax4CompressedWithStrips<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)

22
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -95,6 +95,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel);
}
[Theory]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Ccitt1D)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup4Fax)]
public void EncoderOptions_WithInvalidCompressionAndPixelTypeCombination_DefaultsToRgb(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
{
// arrange
var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image input = new Image<Rgb24>(10, 10);
using var memStream = new MemoryStream();
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel);
}
[Theory]
[InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]

10
tests/ImageSharp.Tests/TestImages.cs

@ -272,6 +272,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg";
public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg";
public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg";
public const string Issue2133_DeduceColorSpace = "Jpg/issues/Issue2133.jpg";
public const string Issue2136_ScanMarkerExtraneousBytes = "Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg";
public static class Fuzz
{
@ -759,7 +761,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff";
public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff";
public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff";
public const string Fax4Compressed2 = "Tiff/CCITTGroup4.tiff";
public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff";
public const string Fax4CompressedMinIsBlack = "Tiff/CCITTGroup4_minisblack.tiff";
public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff";
public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff";
@ -780,6 +784,7 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff";
public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff";
public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.tiff";
public const string RgbJpegCompressed2 = "Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff";
public const string RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff";
public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff";
public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff";
@ -825,11 +830,10 @@ namespace SixLabors.ImageSharp.Tests
public const string FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff";
public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.tiff";
public const string FlowerYCbCr888Contiguoush4v4 = "Tiff/flower-ycbcr-contig-08_h4v4.tiff";
public const string RgbYCbCr888Contiguoush1v1 = "Tiff/rgb-ycbcr-contig-08_h1v1.tiff";
public const string RgbYCbCr888Contiguoush2v1 = "Tiff/rgb-ycbcr-contig-08_h2v1.tiff";
public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff";
public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff";
public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.tiff";
public const string YCbCrJpegCompressed2 = "Tiff/ycbcr_jpegcompressed2.tiff";
public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff";
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";
@ -912,6 +916,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";
public const string Issues2123 = "Tiff/Issues/Issue2123.tiff";
public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h1v1.png

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

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v1.png

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

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v2.png

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

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h4v4.png

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

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-planar-08_h1v1.png

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

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h2v2.png

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

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h4v4.png

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

3
tests/Images/Input/Jpg/issues/Issue2133.jpg

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

3
tests/Images/Input/Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg

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

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

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

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

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

3
tests/Images/Input/Tiff/Issues/Group4CompressionWithStrips.tiff

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

3
tests/Images/Input/Tiff/Issues/Issue2123.tiff

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

3
tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v1.tiff

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

4
tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b81013d7b0a29ed1ac9c33e175e0c0e69494b93b2b65b692f16d9ea042b9d5d
size 7759
oid sha256:3e889209fc31702aaa7c966c1b5370cc0904cbfbcfd17718977045049cc1bfd9
size 5904

3
tests/Images/Input/Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff

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

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

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