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

12
.github/workflows/code-coverage.yml

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

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

@ -75,6 +75,12 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo8(int x) => x & 7; 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> /// <summary>
/// Fast (x mod m) calculator, with the restriction that /// Fast (x mod m) calculator, with the restriction that
/// <paramref name="m"/> should be power of 2. /// <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. /// Tells whether input value is outside of the given range.
/// </summary> /// </summary>
/// <param name="value">Value.</param> /// <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> /// <param name="max">Maximum value, inclusive.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOutOfRange(int value, int min, int max) 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)] [FieldOffset(224)]
public Vector256<float> V7; 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) 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!"); 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<float> bBase = ref b.V0;
ref Vector256<short> destRef = ref dest.V01; 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) 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<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); 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; 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(); c = this.ReadStream();
} }
// Found a marker
// We accept multiple FF bytes followed by a 0 as meaning a single FF data byte. // 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) if (c != 0)
{ {
this.Marker = (byte)c; // It's a trick so we won't read past actual marker
this.badData = true; this.badData = true;
this.Marker = (byte)c;
this.MarkerPosition = this.stream.Position - 2; this.MarkerPosition = this.stream.Position - 2;
} }
} }
@ -199,7 +201,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (b != 0) if (b != 0)
{ {
this.Marker = (byte)b; this.Marker = (byte)b;
this.badData = true;
this.MarkerPosition = this.stream.Position - 2; this.MarkerPosition = this.stream.Position - 2;
return true; 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 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> /// <summary>
/// Apply floating point FDCT inplace using simd operations. /// Apply floating point FDCT inplace using simd operations.
/// </summary> /// </summary>
@ -57,6 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
block.V0 = Avx.Add(tmp10, tmp11); block.V0 = Avx.Add(tmp10, tmp11);
block.V4 = Avx.Subtract(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); Vector256<float> z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071);
block.V2 = Avx.Add(tmp13, z1); block.V2 = Avx.Add(tmp13, z1);
block.V6 = Avx.Subtract(tmp13, z1); block.V6 = Avx.Subtract(tmp13, z1);
@ -66,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
tmp11 = Avx.Add(tmp5, tmp6); tmp11 = Avx.Add(tmp5, tmp6);
tmp12 = Avx.Add(tmp6, tmp7); tmp12 = Avx.Add(tmp6, tmp7);
Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826); Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), Vector256.Create(0.382683433f)); // mm256_F_0_3826
Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10); Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(0.541196100f), tmp10); // mm256_F_0_5411
Vector256<float> z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12); 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> z3 = Avx.Multiply(tmp11, mm256_F_0_7071);
Vector256<float> z11 = Avx.Add(tmp7, z3); 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> tmp10 = Avx.Add(z5, tmp2);
Vector256<float> tmp11 = Avx.Subtract(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> tmp13 = Avx.Add(tmp1, tmp3);
Vector256<float> tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142); 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); tmp7 = Avx.Add(z11, z13);
tmp11 = Avx.Multiply(Avx.Subtract(z11, z13), mm256_F_1_4142); 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); tmp10 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z12, Vector256.Create(-1.082392200f)); // mm256_F_n1_0823
tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, mm256_F_n2_6131); tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, Vector256.Create(-2.613125930f)); // mm256_F_n2_6131
tmp6 = Avx.Subtract(tmp12, tmp7); tmp6 = Avx.Subtract(tmp12, tmp7);
tmp5 = Avx.Subtract(tmp11, tmp6); tmp5 = Avx.Subtract(tmp11, tmp6);

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

@ -90,6 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private JFifMarker jFif; 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> /// <summary>
/// Contains information about the Adobe marker. /// Contains information about the Adobe marker.
/// </summary> /// </summary>
@ -159,38 +164,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Finds the next file marker within the byte stream. /// Finds the next file marker within the byte stream.
/// </summary> /// </summary>
/// <param name="marker">The buffer to read file markers to.</param>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <returns>The <see cref="JpegFileMarker"/></returns> /// <returns>The <see cref="JpegFileMarker"/>.</returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream) public static JpegFileMarker FindNextFileMarker(BufferedReadStream stream)
{ {
int value = stream.Read(marker, 0, 2); while (true)
if (value == 0)
{ {
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) // Found a marker.
{ if (b == 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)
{ {
int suffix = stream.ReadByte(); while (b == JpegConstants.Markers.XFF)
if (suffix == -1)
{ {
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/> /// <inheritdoc/>
@ -340,15 +345,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
} }
stream.Read(this.markerBuffer, 0, 2); fileMarker = FindNextFileMarker(stream);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
this.QuantizationTables ??= new Block8x8F[4]; this.QuantizationTables ??= new Block8x8F[4];
// Break only when we discover a valid EOI marker. // Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695 // https://github.com/SixLabors/ImageSharp/issues/695
while (fileMarker.Marker != JpegConstants.Markers.EOI while (fileMarker.Marker != JpegConstants.Markers.EOI)
|| (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -500,7 +502,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// Read on. // Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, stream); fileMarker = FindNextFileMarker(stream);
} }
} }
@ -528,17 +530,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentCount == 3) 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. // 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) if (this.Components[2].Id == 66 && this.Components[1].Id == 71 && this.Components[0].Id == 82)
{ {
return JpegColorSpace.RGB; 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. // Some images are poorly encoded and contain incorrect colorspace transform metadata.
// We ignore that and always fall back to the default colorspace. // We ignore that and always fall back to the default colorspace.
return JpegColorSpace.YCbCr; return JpegColorSpace.YCbCr;
@ -546,9 +587,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentCount == 4) if (componentCount == 4)
{ {
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck // jfif images doesn't not support 4 component images, so we only check adobe.
? JpegColorSpace.Ycck if (!this.adobe.Equals(default))
: JpegColorSpace.Cmyk; {
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); JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount);
@ -715,6 +771,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining)
{ {
this.hasJFif = true;
// We can only decode JFif identifiers. // We can only decode JFif identifiers.
// Some images contain multiple JFIF markers (Issue 1932) so we check to see // Some images contain multiple JFIF markers (Issue 1932) so we check to see
// if it's already been read. // 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)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported."); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{ {
internal static class BitWriterUtils 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; nint bitPos = Numerics.Modulo8(pos);
int bufferPos = pos / 8; nint bufferPos = pos / 8;
int startIdx = bufferPos + bitPos; nint startIdx = bufferPos + bitPos;
int endIdx = (int)(startIdx + count); nint endIdx = startIdx + count;
if (value == 1) if (value == 1)
{ {
for (int i = startIdx; i < endIdx; i++) for (nint i = startIdx; i < endIdx; i++)
{ {
WriteBit(buffer, bufferPos, bitPos); WriteBit(buffer, bufferPos, bitPos);
@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
else else
{ {
for (int i = startIdx; i < endIdx; i++) for (nint i = startIdx; i < endIdx; i++)
{ {
WriteZeroBit(buffer, bufferPos, bitPos); 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 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 } { 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 } { 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 } { 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 }, { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 },
{ 27, 0x24 }, { 28, 0x18 } { 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 }, { 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 }, { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 }, { 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 }, { 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 } { 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 } { 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 } { 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 } { 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 } { 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 }, { 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 }, { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 },
{ 1600, 0x9A }, { 1728, 0x9B } { 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 } { 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 }, { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } { 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 } { 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 } { 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 }, { 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 }, { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } { 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 }, { 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 }, { 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> /// <summary>
/// Pads output to the next byte /// Pads output to the next byte.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If the output is not currently on a byte boundary, /// 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> /// </remarks>
protected void PadByte() protected void PadByte()
{ {
// Check if padding is necessary. // Check if padding is necessary.
if (this.bitPosition % 8 != 0) if (Numerics.Modulo8(this.bitPosition) != 0)
{ {
// Skip padding bits, move to next byte. // Skip padding bits, move to next byte.
this.bytePosition++; this.bytePosition++;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
@ -130,6 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return index + offset; return index + offset;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int FindB2ForImaginaryWhiteLine() => this.width; private int FindB2ForImaginaryWhiteLine() => this.width;
private int FindB2ForNormalLine(int b1) 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> /// <summary>
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct. /// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
/// </summary> /// </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="bitsRequired">The bits required.</param>
/// <param name="extensionBits">The extension bits.</param> /// <param name="extensionBits">The extension bits.</param>
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) public CcittTwoDimensionalCode(int code, CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); {
this.Code = code;
this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
}
/// <summary> /// <summary>
/// Gets the code type. /// Gets the code type.
/// </summary> /// </summary>
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); 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); jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder? // 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); CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; 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.YCbCr:
case TiffPhotometricInterpretation.Rgb: case TiffPhotometricInterpretation.Rgb:
{ {
@ -82,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None); jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder? // 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); CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }

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

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

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

@ -42,11 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer) 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(); buffer.Clear();
uint bitsWritten = 0; nint bitsWritten = 0;
uint pixelsWritten = 0; nuint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData) while (bitReader.HasMoreData)
{ {
bitReader.ReadNextRun(); bitReader.ReadNextRun();
@ -55,32 +56,39 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
if (bitReader.IsWhiteRun) if (bitReader.IsWhiteRun)
{ {
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
} }
else 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; pixelsWritten += bitReader.RunLength;
} }
if (pixelsWritten == this.Width) if (pixelsWritten == (ulong)this.Width)
{ {
bitReader.StartNewRow(); rowsWritten++;
pixelsWritten = 0; pixelsWritten = 0;
// Write padding bits, if necessary. // Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8); nint pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8) if (pad != 8)
{ {
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad; 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"); 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]; byte repeatData = compressedData[compressedOffset + 1];
int repeatLength = 257 - headerByte; int repeatLength = 257 - headerByte;
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength); buffer.Slice(decompressedOffset, repeatLength).Fill(repeatData);
compressedOffset += 2; compressedOffset += 2;
decompressedOffset += repeatLength; 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/> /// <inheritdoc/>
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); 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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
/// <summary> /// <summary>
/// Bitreader for reading compressed CCITT T4 1D data. /// Bitreader for reading compressed CCITT T4 1D data.
/// </summary> /// </summary>
internal class T4BitReader : IDisposable internal class T4BitReader
{ {
/// <summary> /// <summary>
/// The logical order of bits within a byte. /// The logical order of bits within a byte.
@ -52,28 +49,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
private readonly int maxCodeLength = 13; 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 } { 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 } { 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 } { 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 }, { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 },
{ 0x24, 27 }, { 0x18, 28 } { 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 }, { 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 }, { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 } { 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 }, { 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 }, { 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 } { 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 } { 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 } { 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 } { 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 } { 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 }, { 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 }, { 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 },
{ 0x9A, 1600 }, { 0x9B, 1728 } { 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 } { 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 }, { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } { 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 } { 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 } { 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 }, { 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 }, { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } { 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 }, { 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 }, { 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 } { 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 }
}; };
/// <summary>
/// The compressed input stream.
/// </summary>
private readonly BufferedReadStream stream;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T4BitReader" /> class. /// Initializes a new instance of the <see cref="T4BitReader" /> class.
/// </summary> /// </summary>
/// <param name="input">The compressed input stream.</param> /// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</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="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> /// <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.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.DataLength = bytesToRead; this.DataLength = bytesToRead;
this.BitsRead = 0; this.BitsRead = 0;
this.Value = 0; this.Value = 0;
@ -228,12 +227,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.RunLength = 0; this.RunLength = 0;
this.eolPadding = eolPadding; this.eolPadding = eolPadding;
this.ReadNextByte();
if (this.eolPadding) if (this.eolPadding)
{ {
this.maxCodeLength = 24; this.maxCodeLength = 24;
} }
} }
/// <summary>
/// Gets or sets the byte at the given position.
/// </summary>
private byte DataAtPosition { get; set; }
/// <summary> /// <summary>
/// Gets the current value. /// Gets the current value.
/// </summary> /// </summary>
@ -259,11 +265,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
protected ulong Position { get; set; } protected ulong Position { get; set; }
/// <summary>
/// Gets the compressed image data.
/// </summary>
public IMemoryOwner<byte> Data { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether there is more data to read left. /// Gets a value indicating whether there is more data to read left.
/// </summary> /// </summary>
@ -390,9 +391,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.terminationCodeFound = false; this.terminationCodeFound = false;
} }
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary> /// <summary>
/// An EOL is expected before the first data. /// An EOL is expected before the first data.
/// </summary> /// </summary>
@ -436,6 +434,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
/// <param name="nBits">The number of bits to read.</param> /// <param name="nBits">The number of bits to read.</param>
/// <returns>The value read.</returns> /// <returns>The value read.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected uint ReadValue(int nBits) protected uint ReadValue(int nBits)
{ {
DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits));
@ -452,6 +451,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return v; 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() private uint WhiteTerminatingCodeRunLength()
{ {
switch (this.CurValueBitsRead) switch (this.CurValueBitsRead)
@ -806,44 +819,49 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return false; return false;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint GetBit() private uint GetBit()
{ {
if (this.BitsRead >= 8) if (this.BitsRead >= 8)
{ {
this.LoadNewByte(); this.AdvancePosition();
} }
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.BitsRead - 1; 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++; this.BitsRead++;
return bit; return bit;
} }
private void LoadNewByte() [MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool LoadNewByte()
{ {
this.Position++; if (this.Position < (ulong)this.DataLength)
this.ResetBitsRead();
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(); int nextByte = this.stream.ReadByte();
input.Read(dataSpan, 0, bytesToRead); if (nextByte == -1)
if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst)
{ {
for (int i = 0; i < dataSpan.Length; i++) TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data.");
{
dataSpan[i] = ReverseBits(dataSpan[i]);
}
} }
this.ResetBitsRead();
this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst
? ReverseBits((byte)nextByte)
: (byte)nextByte;
} }
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits // 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); 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(); buffer.Clear();
uint bitsWritten = 0; nint bitsWritten = 0;
uint pixelWritten = 0; nuint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData) while (bitReader.HasMoreData)
{ {
bitReader.ReadNextRun(); bitReader.ReadNextRun();
@ -74,41 +75,47 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
this.WritePixelRun(buffer, bitReader, bitsWritten); this.WritePixelRun(buffer, bitReader, bitsWritten);
bitsWritten += bitReader.RunLength; bitsWritten += (int)bitReader.RunLength;
pixelWritten += bitReader.RunLength; pixelsWritten += bitReader.RunLength;
} }
if (bitReader.IsEndOfScanLine) if (bitReader.IsEndOfScanLine)
{ {
// Write padding bytes, if necessary. // Write padding bytes, if necessary.
uint pad = 8 - (bitsWritten % 8); nint pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8) if (pad != 8)
{ {
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad; 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. // 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(); bitReader.ReadNextRun();
this.WritePixelRun(buffer, bitReader, bitsWritten); 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) if (bitReader.IsWhiteRun)
{ {
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
} }
else 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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Runtime.CompilerServices;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
@ -16,38 +15,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
private readonly int maxCodeLength = 12; 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>() private static readonly CcittTwoDimensionalCode Len1Code1 = new(0b1, CcittTwoDimensionalCodeType.Vertical0, 1);
{
{ 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len3Codes = new Dictionary<uint, CcittTwoDimensionalCode>() private static readonly CcittTwoDimensionalCode Len3Code001 = new(0b001, CcittTwoDimensionalCodeType.Horizontal, 3);
{ private static readonly CcittTwoDimensionalCode Len3Code010 = new(0b010, CcittTwoDimensionalCodeType.VerticalL1, 3);
{ 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) }, private static readonly CcittTwoDimensionalCode Len3Code011 = new(0b011, CcittTwoDimensionalCodeType.VerticalR1, 3);
{ 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) },
{ 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len4Codes = new Dictionary<uint, CcittTwoDimensionalCode>() private static readonly CcittTwoDimensionalCode Len4Code0001 = new(0b0001, CcittTwoDimensionalCodeType.Pass, 4);
{
{ 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len6Codes = new Dictionary<uint, CcittTwoDimensionalCode>() private static readonly CcittTwoDimensionalCode Len6Code000011 = new(0b000011, CcittTwoDimensionalCodeType.VerticalR2, 6);
{ private static readonly CcittTwoDimensionalCode Len6Code000010 = new(0b000010, CcittTwoDimensionalCodeType.VerticalL2, 6);
{ 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) },
{ 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len7Codes = new Dictionary<uint, CcittTwoDimensionalCode>() private static readonly CcittTwoDimensionalCode Len7Code0000011 = new(0b0000011, CcittTwoDimensionalCodeType.VerticalR3, 7);
{ private static readonly CcittTwoDimensionalCode Len7Code0000010 = new(0b0000010, CcittTwoDimensionalCodeType.VerticalL3, 7);
{ 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) }, private static readonly CcittTwoDimensionalCode Len7Code0000001 = new(0b0000001, CcittTwoDimensionalCodeType.Extensions2D, 7);
{ 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) }, private static readonly CcittTwoDimensionalCode Len7Code0000000 = new(0b0000000, CcittTwoDimensionalCodeType.Extensions1D, 7);
{ 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) },
{ 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) }
};
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T6BitReader"/> class. /// 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="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</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="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param> public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator) : base(input, fillOrder, bytesToRead)
: base(input, fillOrder, bytesToRead, allocator)
{ {
} }
/// <inheritdoc/> /// <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> /// <summary>
/// Gets or sets the two dimensional code. /// Gets or sets the two dimensional code.
@ -85,45 +68,81 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
switch (this.CurValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 1: case 1:
if (Len1Codes.ContainsKey(value)) if (value == Len1Code1.Code)
{ {
this.Code = Len1Codes[value]; this.Code = Len1Code1;
return false; return false;
} }
break; break;
case 3: 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; return false;
} }
break; break;
case 4: case 4:
if (Len4Codes.ContainsKey(value)) if (value == Len4Code0001.Code)
{ {
this.Code = Len4Codes[value]; this.Code = Len4Code0001;
return false; return false;
} }
break; break;
case 6: 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; return false;
} }
break; break;
case 7: 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; return false;
} }
@ -154,6 +173,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Swaps the white run to black run an vise versa. /// Swaps the white run to black run an vise versa.
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun; 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -15,12 +17,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
private readonly bool isWhiteZero; private readonly bool isWhiteZero;
private readonly byte whiteValue;
private readonly byte blackValue;
private readonly int width; private readonly int width;
private readonly byte white;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T6TiffCompression" /> class. /// Initializes a new instance of the <see cref="T6TiffCompression" /> class.
/// </summary> /// </summary>
@ -40,8 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.FillOrder = fillOrder; this.FillOrder = fillOrder;
this.width = width; this.width = width;
this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1); this.white = (byte)(this.isWhiteZero ? 0 : 255);
this.blackValue = (byte)(this.isWhiteZero ? 1 : 0);
} }
/// <summary> /// <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) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{ {
int height = stripHeight; int height = stripHeight;
buffer.Clear();
using System.Buffers.IMemoryOwner<byte> scanLineBuffer = this.Allocator.Allocate<byte>(this.width * 2); using System.Buffers.IMemoryOwner<byte> scanLineBuffer = this.Allocator.Allocate<byte>(this.width * 2);
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width); Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width);
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, 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); var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width);
uint bitsWritten = 0; nint bitsWritten = 0;
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
scanLine.Clear(); 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); nint bitPos = Numerics.Modulo8(bitsWritten);
for (int i = 0; i < scanLine.Length; i++) 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++; bitsWritten++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
} }
// Write padding bytes, if necessary. // Write padding bytes, if necessary.
uint remainder = bitsWritten % 8; nint remainder = Numerics.Modulo8(bitsWritten);
if (remainder != 0) if (remainder != 0)
{ {
uint padding = 8 - remainder; nint padding = 8 - remainder;
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, padding, 0); BitWriterUtils.WriteBits(buffer, bitsWritten, padding, 0);
bitsWritten += padding; bitsWritten += padding;
} }
@ -122,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
else else
{ {
scanline.Fill((byte)255); scanline.Fill(255);
} }
break; break;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; L16 l16 = TiffUtils.L16Default;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -17,26 +19,65 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
Color black = Color.Black; ref byte dataRef = ref MemoryMarshal.GetReference(data);
Color white = Color.White; for (nint y = top; y < top + height; y++)
for (int 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++]; byte b = Unsafe.Add(ref dataRef, offset++);
int maxShift = Math.Min(left + width - x, 8); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,14 +26,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx); Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) 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;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int offset = 0; int offset = 0;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) 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); var l8 = default(L8);
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;) for (int x = left; x < left + width - 1;)
{ {
byte byteData = data[offset++]; byte byteData = data[offset++];
@ -32,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1; l8.PackedValue = intensity1;
color.FromL8(l8); color.FromL8(l8);
pixels[x++, y] = color; pixelRowSpan[x++] = color;
byte intensity2 = (byte)((byteData & 0x0F) * 17); byte intensity2 = (byte)((byteData & 0x0F) * 17);
l8.PackedValue = intensity2; l8.PackedValue = intensity2;
color.FromL8(l8); color.FromL8(l8);
pixels[x++, y] = color; pixelRowSpan[x++] = color;
} }
if (isOddWidth) if (isOddWidth)
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1; l8.PackedValue = intensity1;
color.FromL8(l8); 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 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="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> /// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap) public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap)
@ -56,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int i = 0; i < palette.Length; i++) for (int i = 0; i < palette.Length; i++)
{ {
float r = colorMap[rOffset + i] / 65535F; float r = colorMap[rOffset + i] * InvMax;
float g = colorMap[gOffset + i] / 65535F; float g = colorMap[gOffset + i] * InvMax;
float b = colorMap[bOffset + i] / 65535F; float b = colorMap[bOffset + i] * InvMax;
palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f)); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;

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

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -26,11 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

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

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) 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;
using System.Buffers; using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].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/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
byte[] buffer = new byte[4]; 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/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;

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

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -33,11 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -32,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;

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

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -33,10 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -32,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;

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

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -33,10 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].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; bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null; using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty; Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++) 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/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
byte[] buffer = new byte[4]; 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,11 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; L16 l16 = TiffUtils.L16Default;
var color = default(TPixel); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -16,26 +18,65 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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; colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
Color black = Color.Black; ref byte dataRef = ref MemoryMarshal.GetReference(data);
Color white = Color.White; for (nint y = top; y < top + height; y++)
for (int 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++]; byte b = Unsafe.Add(ref dataRef, offset++);
int maxShift = Math.Min(left + width - x, 8); 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,15 +26,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
const uint maxValue = 0xFFFFFF; const uint maxValue = 0xFFFFFF;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx); Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) 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/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int offset = 0; int offset = 0;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) 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); var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default); color.FromScaledVector4(Vector4.Zero);
const uint maxValue = 0xFFFFFFFF; const uint maxValue = 0xFFFFFFFF;
int offset = 0; 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); var l8 = default(L8);
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;) for (int x = left; x < left + width - 1;)
{ {
byte byteData = data[offset++]; byte byteData = data[offset++];
@ -32,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1; l8.PackedValue = intensity1;
color.FromL8(l8); color.FromL8(l8);
pixels[x++, y] = color; pixelRowSpan[x++] = color;
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17); byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
l8.PackedValue = intensity2; l8.PackedValue = intensity2;
color.FromL8(l8); color.FromL8(l8);
pixels[x++, y] = color; pixelRowSpan[x++] = color;
} }
if (isOddWidth) if (isOddWidth)
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1; l8.PackedValue = intensity1;
color.FromL8(l8); 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 = private static readonly Rational[] DefaultLuma =
{ {
new Rational(299, 1000), new(299, 1000),
new Rational(587, 1000), new(587, 1000),
new Rational(114, 1000) new(114, 1000)
}; };
private static readonly Rational[] DefaultReferenceBlackWhite = private static readonly Rational[] DefaultReferenceBlackWhite =
{ {
new Rational(0, 1), new Rational(255, 1), new(0, 1), new(255, 1),
new Rational(128, 1), new Rational(255, 1), new(128, 1), new(255, 1),
new Rational(128, 1), new Rational(255, 1) new(128, 1), new(255, 1)
}; };
public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients) 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(); Span<byte> tmpBufferSpan = tmpBuffer.GetSpan();
ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan); ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan);
ycbcrData = 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); var color = default(TPixel);
int offset = 0; int offset = 0;
int widthPadding = 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> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param> /// <param name="tags">The IFD tags.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</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) private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {

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

@ -459,6 +459,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffCompression.Jpeg: case TiffCompression.Jpeg:
{ {
options.CompressionType = TiffDecoderCompressionType.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; break;
} }

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

@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerPixel) switch (bitsPerPixel)
{ {
case TiffBitsPerPixel.Bit1: 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. // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None); this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
@ -375,6 +375,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break; 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; return;
} }
@ -396,18 +403,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero: case TiffPhotometricInterpretation.WhiteIsZero:
if (this.CompressionType == TiffCompression.Ccitt1D || if (IsOneBitCompression(this.CompressionType))
this.CompressionType == TiffCompression.CcittGroup3Fax ||
this.CompressionType == TiffCompression.CcittGroup4Fax)
{ {
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None); this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None);
return; return;
} }
else
{ this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); return;
return;
}
case TiffPhotometricInterpretation.PaletteColor: case TiffPhotometricInterpretation.PaletteColor:
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
@ -428,5 +431,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.CompressionType = compression; this.CompressionType = compression;
this.HorizontalPredictor = predictor; 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; 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 Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0);
public static L16 L16Default { get; } = new(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 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) public static void CollectColorBlueTransforms(Span<uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span<int> histo)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
@ -50,8 +20,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
const int span = 16; const int span = 16;
Span<ushort> values = stackalloc ushort[span]; Span<ushort> values = stackalloc ushort[span];
var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); 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);
var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); 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++) for (int y = 0; y < tileHeight; y++)
{ {
Span<uint> srcSpan = bgra.Slice(y * stride); Span<uint> srcSpan = bgra.Slice(y * stride);
@ -62,18 +37,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2); nint input1Idx = x + (span / 2);
Vector256<byte> input0 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); 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> input1 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector256<byte> r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); Vector256<byte> r0 = Avx2.Shuffle(input0, collectColorBlueTransformsShuffleLowMask256);
Vector256<byte> r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); Vector256<byte> r1 = Avx2.Shuffle(input1, collectColorBlueTransformsShuffleHighMask256);
Vector256<byte> r = Avx2.Or(r0, r1); Vector256<byte> r = Avx2.Or(r0, r1);
Vector256<byte> gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); Vector256<byte> gb0 = Avx2.And(input0, collectColorBlueTransformsGreenBlueMask256);
Vector256<byte> gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); Vector256<byte> gb1 = Avx2.And(input1, collectColorBlueTransformsGreenBlueMask256);
Vector256<ushort> gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); 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> a = Avx2.MultiplyHigh(r.AsInt16(), multsr);
Vector256<short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg); Vector256<short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg);
Vector256<byte> c = Avx2.Subtract(gb.AsByte(), b.AsByte()); Vector256<byte> c = Avx2.Subtract(gb.AsByte(), b.AsByte());
Vector256<byte> d = Avx2.Subtract(c, a.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); ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector256<ushort>>(ref outputRef) = e.AsUInt16(); Unsafe.As<ushort, Vector256<ushort>>(ref outputRef) = e.AsUInt16();
@ -95,8 +70,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
const int span = 8; const int span = 8;
Span<ushort> values = stackalloc ushort[span]; Span<ushort> values = stackalloc ushort[span];
var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); Vector128<byte> collectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255);
var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); 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++) for (int y = 0; y < tileHeight; y++)
{ {
Span<uint> srcSpan = bgra.Slice(y * stride); Span<uint> srcSpan = bgra.Slice(y * stride);
@ -107,18 +87,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2); nint input1Idx = x + (span / 2);
Vector128<byte> input0 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); 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> input1 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector128<byte> r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask); Vector128<byte> r0 = Ssse3.Shuffle(input0, collectColorBlueTransformsShuffleLowMask);
Vector128<byte> r1 = Ssse3.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask); Vector128<byte> r1 = Ssse3.Shuffle(input1, collectColorBlueTransformsShuffleHighMask);
Vector128<byte> r = Sse2.Or(r0, r1); Vector128<byte> r = Sse2.Or(r0, r1);
Vector128<byte> gb0 = Sse2.And(input0, CollectColorBlueTransformsGreenBlueMask); Vector128<byte> gb0 = Sse2.And(input0, collectColorBlueTransformsGreenBlueMask);
Vector128<byte> gb1 = Sse2.And(input1, CollectColorBlueTransformsGreenBlueMask); Vector128<byte> gb1 = Sse2.And(input1, collectColorBlueTransformsGreenBlueMask);
Vector128<ushort> gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); 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> a = Sse2.MultiplyHigh(r.AsInt16(), multsr);
Vector128<short> b = Sse2.MultiplyHigh(g.AsInt16(), multsg); Vector128<short> b = Sse2.MultiplyHigh(g.AsInt16(), multsg);
Vector128<byte> c = Sse2.Subtract(gb.AsByte(), b.AsByte()); Vector128<byte> c = Sse2.Subtract(gb.AsByte(), b.AsByte());
Vector128<byte> d = Sse2.Subtract(c, a.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); ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector128<ushort>>(ref outputRef) = e.AsUInt16(); Unsafe.As<ushort, Vector128<ushort>>(ref outputRef) = e.AsUInt16();
@ -163,7 +143,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && tileWidth >= 16) 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; const int span = 16;
Span<ushort> values = stackalloc ushort[span]; Span<ushort> values = stackalloc ushort[span];
for (int y = 0; y < tileHeight; y++) for (int y = 0; y < tileHeight; y++)
@ -176,15 +158,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2); nint input1Idx = x + (span / 2);
Vector256<byte> input0 = Unsafe.As<uint, Vector256<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); 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> 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> g0 = Avx2.And(input0, collectColorRedTransformsGreenMask256); // 0 0 | g 0
Vector256<byte> g1 = Avx2.And(input1, CollectColorRedTransformsGreenMask256); Vector256<byte> g1 = Avx2.And(input1, collectColorRedTransformsGreenMask256);
Vector256<ushort> g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 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> a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r
Vector256<int> a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16); Vector256<int> a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16);
Vector256<ushort> a = Avx2.PackUnsignedSaturate(a0, a1); // x r Vector256<ushort> a = Avx2.PackUnsignedSaturate(a0, a1); // x r
Vector256<short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr Vector256<short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr
Vector256<byte> c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r' 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); ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector256<ushort>>(ref outputRef) = d.AsUInt16(); Unsafe.As<ushort, Vector256<ushort>>(ref outputRef) = d.AsUInt16();
@ -204,7 +186,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
else if (Sse41.IsSupported) 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; const int span = 8;
Span<ushort> values = stackalloc ushort[span]; Span<ushort> values = stackalloc ushort[span];
for (int y = 0; y < tileHeight; y++) for (int y = 0; y < tileHeight; y++)
@ -217,15 +201,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint input1Idx = x + (span / 2); nint input1Idx = x + (span / 2);
Vector128<byte> input0 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); 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> 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> g0 = Sse2.And(input0, collectColorRedTransformsGreenMask); // 0 0 | g 0
Vector128<byte> g1 = Sse2.And(input1, CollectColorRedTransformsGreenMask); Vector128<byte> g1 = Sse2.And(input1, collectColorRedTransformsGreenMask);
Vector128<ushort> g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 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> a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r
Vector128<int> a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); Vector128<int> a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16);
Vector128<ushort> a = Sse41.PackUnsignedSaturate(a0, a1); // x r Vector128<ushort> a = Sse41.PackUnsignedSaturate(a0, a1); // x r
Vector128<short> b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr Vector128<short> b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr
Vector128<byte> c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' 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); ref ushort outputRef = ref MemoryMarshal.GetReference(values);
Unsafe.As<ushort, Vector128<ushort>>(ref outputRef) = d.AsUInt16(); 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; 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> /// <summary>
/// Returns the exact index where array1 and array2 are different. For an index /// 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 /// 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 SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported) 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; int numPixels = pixelData.Length;
nint i; nint i;
for (i = 0; i <= numPixels - 8; i += 8) for (i = 0; i <= numPixels - 8; i += 8)
{ {
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector256<byte> input = Unsafe.As<uint, Vector256<uint>>(ref pos).AsByte(); 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); Vector256<byte> output = Avx2.Add(input, in0g0g);
Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32();
} }
@ -147,13 +118,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
else if (Ssse3.IsSupported) 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; int numPixels = pixelData.Length;
nint i; nint i;
for (i = 0; i <= numPixels - 4; i += 4) for (i = 0; i <= numPixels - 4; i += 4)
{ {
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte(); 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); Vector128<byte> output = Sse2.Add(input, in0g0g);
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
} }
@ -169,11 +141,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint i; nint i;
for (i = 0; i <= numPixels - 4; i += 4) 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); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte(); 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> a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g
Vector128<ushort> b = Sse2.ShuffleLow(a, AddGreenToBlueAndRedShuffleMask); Vector128<ushort> b = Sse2.ShuffleLow(a, mmShuffle_2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, AddGreenToBlueAndRedShuffleMask); // 0g0g Vector128<ushort> c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g
Vector128<byte> output = Sse2.Add(input.AsByte(), c.AsByte()); Vector128<byte> output = Sse2.Add(input.AsByte(), c.AsByte());
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
} }
@ -209,13 +183,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported) 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; int numPixels = pixelData.Length;
nint i; nint i;
for (i = 0; i <= numPixels - 8; i += 8) for (i = 0; i <= numPixels - 8; i += 8)
{ {
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector256<byte> input = Unsafe.As<uint, Vector256<uint>>(ref pos).AsByte(); 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); Vector256<byte> output = Avx2.Subtract(input, in0g0g);
Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32();
} }
@ -227,13 +202,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
else if (Ssse3.IsSupported) 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; int numPixels = pixelData.Length;
nint i; nint i;
for (i = 0; i <= numPixels - 4; i += 4) for (i = 0; i <= numPixels - 4; i += 4)
{ {
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte(); 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); Vector128<byte> output = Sse2.Subtract(input, in0g0g);
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
} }
@ -249,11 +225,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
nint i; nint i;
for (i = 0; i <= numPixels - 4; i += 4) 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); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte(); 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> a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g
Vector128<ushort> b = Sse2.ShuffleLow(a, SubtractGreenFromBlueAndRedShuffleMask); Vector128<ushort> b = Sse2.ShuffleLow(a, mmShuffle_2200);
Vector128<ushort> c = Sse2.ShuffleHigh(b, SubtractGreenFromBlueAndRedShuffleMask); // 0g0g Vector128<ushort> c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g
Vector128<byte> output = Sse2.Subtract(input.AsByte(), c.AsByte()); Vector128<byte> output = Sse2.Subtract(input.AsByte(), c.AsByte());
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
} }
@ -400,23 +378,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && numPixels >= 8) 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> multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0);
nint idx; nint idx;
for (idx = 0; idx <= numPixels - 8; idx += 8) 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); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos); Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos);
Vector256<byte> a = Avx2.And(input.AsByte(), TransformColorAlphaGreenMask256); Vector256<byte> a = Avx2.And(input.AsByte(), transformColorAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector256<short> e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); Vector256<short> e = Avx2.ShiftLeftLogical(input.AsInt16(), 8);
Vector256<short> f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); Vector256<short> f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
Vector256<int> g = Avx2.ShiftRightLogical(f.AsInt32(), 16); Vector256<int> g = Avx2.ShiftRightLogical(f.AsInt32(), 16);
Vector256<byte> h = Avx2.Add(g.AsByte(), d.AsByte()); 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); Vector256<byte> output = Avx2.Subtract(input.AsByte(), i);
Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector256<uint>>(ref pos) = output.AsUInt32();
} }
@ -428,22 +410,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
else if (Sse2.IsSupported) 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> multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0);
nint idx; nint idx;
for (idx = 0; idx <= numPixels - 4; idx += 4) 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); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos); Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos);
Vector128<byte> a = Sse2.And(input.AsByte(), TransformColorAlphaGreenMask); Vector128<byte> a = Sse2.And(input.AsByte(), transformColorAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<short> e = Sse2.ShiftLeftLogical(input.AsInt16(), 8); Vector128<short> e = Sse2.ShiftLeftLogical(input.AsInt16(), 8);
Vector128<short> f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); Vector128<short> f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
Vector128<int> g = Sse2.ShiftRightLogical(f.AsInt32(), 16); Vector128<int> g = Sse2.ShiftRightLogical(f.AsInt32(), 16);
Vector128<byte> h = Sse2.Add(g.AsByte(), d.AsByte()); 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); Vector128<byte> output = Sse2.Subtract(input.AsByte(), i);
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32(); Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
} }
@ -488,16 +474,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && pixelData.Length >= 8) 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> multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); Vector256<int> multsb2 = MkCst32(Cst5b(m.RedToBlue), 0);
nint idx; nint idx;
for (idx = 0; idx <= pixelData.Length - 8; idx += 8) 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); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos); Vector256<uint> input = Unsafe.As<uint, Vector256<uint>>(ref pos);
Vector256<byte> a = Avx2.And(input.AsByte(), TransformColorInverseAlphaGreenMask256); Vector256<byte> a = Avx2.And(input.AsByte(), transformColorInverseAlphaGreenMask256);
Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); Vector256<short> b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); Vector256<short> c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); Vector256<short> d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector256<byte> e = Avx2.Add(input.AsByte(), d.AsByte()); Vector256<byte> e = Avx2.Add(input.AsByte(), d.AsByte());
Vector256<short> f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); Vector256<short> f = Avx2.ShiftLeftLogical(e.AsInt16(), 8);
@ -516,17 +505,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
else if (Sse2.IsSupported) 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> multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue));
Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); Vector128<int> multsb2 = MkCst16(Cst5b(m.RedToBlue), 0);
nint idx; nint idx;
for (idx = 0; idx <= pixelData.Length - 4; idx += 4) 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); ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx);
Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos); Vector128<uint> input = Unsafe.As<uint, Vector128<uint>>(ref pos);
Vector128<byte> a = Sse2.And(input.AsByte(), TransformColorInverseAlphaGreenMask); Vector128<byte> a = Sse2.And(input.AsByte(), transformColorInverseAlphaGreenMask);
Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); Vector128<short> b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200);
Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); Vector128<short> c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200);
Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); Vector128<short> d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<byte> e = Sse2.Add(input.AsByte(), d.AsByte()); Vector128<byte> e = Sse2.Add(input.AsByte(), d.AsByte());
Vector128<short> f = Sse2.ShiftLeftLogical(e.AsInt16(), 8); 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 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. // Note: method name in libwebp reference implementation is called VP8SSE16x16.
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8_Sse16X16(Span<byte> a, Span<byte> b) 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> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(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 // 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> c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2); Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3.AsInt16(), c4); Vector128<short> c = Sse2.Add(c3.AsInt16(), c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 // 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> d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2); Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4); Vector128<short> d = Sse2.Add(d3, d4);
@ -1050,20 +1030,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Horizontal pass and subsequent transpose. // Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications. // 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()); a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16()); b = Sse2.Subtract(dc, t2.AsInt16());
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1);
c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
c4 = Sse2.Subtract(c1, c2); c4 = Sse2.Subtract(c1, c2);
c = Sse2.Add(c3, c4); c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2);
d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
d4 = Sse2.Add(d1, d2); d4 = Sse2.Add(d1, d2);
d = Sse2.Add(d3, d4); 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> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(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 // 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> c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2); Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3.AsInt16(), c4); Vector128<short> c = Sse2.Add(c3.AsInt16(), c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 // 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> d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2); Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4); Vector128<short> d = Sse2.Add(d3, d4);
@ -1171,20 +1154,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Horizontal pass and subsequent transpose. // Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications. // 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()); a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16()); b = Sse2.Subtract(dc, t2.AsInt16());
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1);
c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
c4 = Sse2.Subtract(c1, c2); c4 = Sse2.Subtract(c1, c2);
c = Sse2.Add(c3, c4); c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2);
d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
d4 = Sse2.Add(d1, d2); d4 = Sse2.Add(d1, d2);
d = Sse2.Add(d3, d4); d = Sse2.Add(d3, d4);
@ -1810,6 +1793,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Ssse3.IsSupported) 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> 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> 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))); 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> b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8);
Vector128<short> b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); Vector128<short> b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8);
Vector128<short> b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); Vector128<short> b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8);
Vector128<byte> c0 = Sse2.And(a0, Mean16x4Mask); // lo byte Vector128<byte> c0 = Sse2.And(a0, mean16x4Mask); // lo byte
Vector128<byte> c1 = Sse2.And(a1, Mean16x4Mask); Vector128<byte> c1 = Sse2.And(a1, mean16x4Mask);
Vector128<byte> c2 = Sse2.And(a2, Mean16x4Mask); Vector128<byte> c2 = Sse2.And(a2, mean16x4Mask);
Vector128<byte> c3 = Sse2.And(a3, Mean16x4Mask); Vector128<byte> c3 = Sse2.And(a3, mean16x4Mask);
Vector128<int> d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); Vector128<int> d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32());
Vector128<int> d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); Vector128<int> d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32());
Vector128<int> d2 = Sse2.Add(b2.AsInt32(), c2.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) // 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) 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). // Convert p1/q1 to byte (for GetBaseDelta).
Vector128<byte> p1s = Sse2.Xor(p1, SignBit); Vector128<byte> p1s = Sse2.Xor(p1, signBit);
Vector128<byte> q1s = Sse2.Xor(q1, SignBit); Vector128<byte> q1s = Sse2.Xor(q1, signBit);
Vector128<byte> mask = NeedsFilter(p1, p0, q0, q1, thresh); Vector128<byte> mask = NeedsFilter(p1, p0, q0, q1, thresh);
// Flip sign. // Flip sign.
p0 = Sse2.Xor(p0, SignBit); p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, SignBit); q0 = Sse2.Xor(q0, signBit);
Vector128<byte> a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); 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); DoSimpleFilterSse2(ref p0, ref q0, a);
// Flip sign. // Flip sign.
p0 = Sse2.Xor(p0, SignBit); p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, SignBit); q0 = Sse2.Xor(q0, signBit);
} }
// Applies filter on 4 pixels (p1, p0, q0 and q1) // Applies filter on 4 pixels (p1, p0, q0 and q1)
@ -2005,11 +1992,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Compute hev mask. // Compute hev mask.
Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh);
Vector128<byte> signBit = Vector128.Create((byte)0x80);
// Convert to signed values. // Convert to signed values.
p1 = Sse2.Xor(p1, SignBit); p1 = Sse2.Xor(p1, signBit);
p0 = Sse2.Xor(p0, SignBit); p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, SignBit); q0 = Sse2.Xor(q0, signBit);
q1 = Sse2.Xor(q1, SignBit); q1 = Sse2.Xor(q1, signBit);
Vector128<sbyte> t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1 Vector128<sbyte> t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1
t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(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.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0)
t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about. 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 t2 = Sse2.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3
Vector128<sbyte> t3 = Sse2.AddSaturate(t1, FourSByte); // 3 * (q0 - p0) + hev(p1 - q1) + 4 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 t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3
t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3
p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2
q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3 q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3
p0 = Sse2.Xor(p0, SignBit); p0 = Sse2.Xor(p0, signBit);
q0 = Sse2.Xor(q0, SignBit); q0 = Sse2.Xor(q0, signBit);
// This is equivalent to signed (a + 1) >> 1 calculation. // 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.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 t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev
q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3 q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3
p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3 p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3
p1 = Sse2.Xor(p1.AsByte(), SignBit); p1 = Sse2.Xor(p1.AsByte(), signBit);
q1 = Sse2.Xor(q1.AsByte(), SignBit); q1 = Sse2.Xor(q1.AsByte(), signBit);
} }
// Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) // 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); Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh);
// Convert to signed values. // Convert to signed values.
p1 = Sse2.Xor(p1, SignBit); Vector128<byte> signBit = Vector128.Create((byte)0x80);
p0 = Sse2.Xor(p0, SignBit); p1 = Sse2.Xor(p1, signBit);
q0 = Sse2.Xor(q0, SignBit); p0 = Sse2.Xor(p0, signBit);
q1 = Sse2.Xor(q1, SignBit); q0 = Sse2.Xor(q0, signBit);
p2 = Sse2.Xor(p2, SignBit); q1 = Sse2.Xor(q1, signBit);
q2 = Sse2.Xor(q2, SignBit); p2 = Sse2.Xor(p2, signBit);
q2 = Sse2.Xor(q2, signBit);
Vector128<sbyte> a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); 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> flow = Sse2.UnpackLow(Vector128<byte>.Zero, f);
Vector128<byte> fhigh = Sse2.UnpackHigh(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> nine = Vector128.Create((short)0x0900);
Vector128<short> f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9 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> sixtyThree = Vector128.Create((short)63);
Vector128<short> a2High = Sse2.Add(f9High, SixtyThree.AsInt16()); // Filter * 9 + 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> a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63
Vector128<short> a1High = Sse2.Add(a2High, f9High); // // 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) 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> v3 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte());
Vector128<sbyte> v4 = Sse2.AddSaturate(fl.AsSByte(), FourSByte); Vector128<sbyte> v4 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte());
v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3 v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3
v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 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). // 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) 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> a1Low = Sse2.ShiftRightArithmetic(a0Low, 7);
Vector128<short> a1High = Sse2.ShiftRightArithmetic(a0High, 7); Vector128<short> a1High = Sse2.ShiftRightArithmetic(a0High, 7);
Vector128<sbyte> delta = Sse2.PackSignedSaturate(a1Low, a1High); Vector128<sbyte> delta = Sse2.PackSignedSaturate(a1Low, a1High);
pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte(); pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte();
qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte(); qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte();
pi = Sse2.Xor(pi, SignBit.AsByte()); pi = Sse2.Xor(pi, signBit.AsByte());
qi = Sse2.Xor(qi, SignBit.AsByte()); qi = Sse2.Xor(qi, signBit.AsByte());
} }
[MethodImpl(InliningOptions.ShortMethod)] [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; 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 // 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. // 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 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()); Vector256<short> out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32());
// if (coeff > 2047) coeff = 2047 // if (coeff > 2047) coeff = 2047
out0 = Avx2.Min(out0, MaxCoeff2047Vec256); out0 = Avx2.Min(out0, Vector256.Create((short)MaxLevel));
// Put the sign back. // Put the sign back.
out0 = Avx2.Sign(out0, input0); out0 = Avx2.Sign(out0, input0);
@ -585,8 +567,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Unsafe.As<short, Vector256<short>>(ref inputRef) = input0; Unsafe.As<short, Vector256<short>>(ref inputRef) = input0;
// zigzag the output before storing it. // zigzag the output before storing it.
Vector256<byte> tmp256 = Avx2.Shuffle(out0.AsByte(), Cst256); 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(), Cst78); 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. // Reverse the order of the 16-byte lanes.
Vector256<byte> tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1); 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()); Vector128<short> out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32());
// if (coeff > 2047) coeff = 2047 // if (coeff > 2047) coeff = 2047
out0 = Sse2.Min(out0, MaxCoeff2047); Vector128<short> maxCoeff2047 = Vector128.Create((short)MaxLevel);
out8 = Sse2.Min(out8, MaxCoeff2047); out0 = Sse2.Min(out0, maxCoeff2047);
out8 = Sse2.Min(out8, maxCoeff2047);
// Put the sign back. // Put the sign back.
out0 = Ssse3.Sign(out0, input0); 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 // There's only two misplaced entries ([8] and [7]) that are crossing the
// reg's boundaries. // reg's boundaries.
// We use pshufb instead of pshuflo/pshufhi. // We use pshufb instead of pshuflo/pshufhi.
Vector128<byte> tmpLo = Ssse3.Shuffle(out0.AsByte(), CstLo); 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(), Cst7); // extract #7 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(), CstHi); 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(), Cst8); // extract #8 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> outZ0 = Sse2.Or(tmpLo, tmp8);
Vector128<byte> outZ8 = Sse2.Or(tmpHi, tmp7); 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 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; 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 }; public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 };
#if SUPPORTS_RUNTIME_INTRINSICS private static byte[] GetClip1()
#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()
{ {
byte[] clip1 = new byte[255 + 510 + 1];
for (int i = -255; i <= 255 + 255; i++) for (int i = -255; i <= 255 + 255; i++)
{ {
Clip1[255 + i] = Clip8b(i); clip1[255 + i] = Clip8b(i);
} }
return clip1;
} }
// Transforms (Paragraph 14.4) // 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> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(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 // 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> c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2); Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3, c4); Vector128<short> c = Sse2.Add(c3, c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 // 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> d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2); Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4); 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) 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> a = Sse2.Add(dc, t2.AsInt16());
Vector128<short> b = Sse2.Subtract(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 // 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> c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2);
Vector128<short> c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); Vector128<short> c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1);
Vector128<short> c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); Vector128<short> c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2); Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3, c4); Vector128<short> c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 // 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> d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1);
Vector128<short> d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); Vector128<short> d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2);
Vector128<short> d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); Vector128<short> d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2); Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4); 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 // *in01 = 00 01 10 11 02 03 12 13
// *in23 = 20 21 30 31 22 23 32 33 // *in23 = 20 21 30 31 22 23 32 33
Vector128<short> shuf01_p = Sse2.ShuffleHigh(row01, MmShuffle2301); const byte mmShuffle_2301 = 0b_10_11_00_01;
Vector128<short> shuf32_p = Sse2.ShuffleHigh(row23, MmShuffle2301); 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 // 00 01 10 11 03 02 13 12
// 20 21 30 31 23 22 33 32 // 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 | ...] = [a0 a1 | a0' a1' | ... ]
// [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] // [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, ... ] // [ (a0 + a1) << 3, ... ]
Vector128<int> tmp11 = Sse2.MultiplyAddAdjacent(a32, K5352_2217p); 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
Vector128<int> tmp31 = Sse2.MultiplyAddAdjacent(a32, K5352_2217m);
Vector128<int> tmp12 = Sse2.Add(tmp11, K1812); // [ (a0 - a1) << 3, ... ]
Vector128<int> tmp32 = Sse2.Add(tmp31, K937); 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> tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9);
Vector128<int> tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9); Vector128<int> tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9);
Vector128<short> s03 = Sse2.PackSignedSaturate(tmp0, tmp2); 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<short> shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3
Vector128<int> v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32()); Vector128<int> v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32());
out01 = Sse2.UnpackLow(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) 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<long> a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64());
Vector128<short> b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16()); Vector128<short> b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16());
Vector128<int> c1 = Sse2.MultiplyAddAdjacent(b23, K5352_2217); 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, K2217_5352); 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, K12000PlusOne); Vector128<int> d1 = Sse2.Add(c1, Vector128.Create(12000 + (1 << 16))); // K12000PlusOne
Vector128<int> d3 = Sse2.Add(c3, K51000); Vector128<int> d3 = Sse2.Add(c3, Vector128.Create(51000));
Vector128<int> e1 = Sse2.ShiftRightArithmetic(d1, 16); Vector128<int> e1 = Sse2.ShiftRightArithmetic(d1, 16);
Vector128<int> e3 = Sse2.ShiftRightArithmetic(d3, 16); Vector128<int> e3 = Sse2.ShiftRightArithmetic(d3, 16);
@ -623,7 +604,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// a0 = v0 + v3 // a0 = v0 + v3
// a1 = v1 + v2 // a1 = v1 + v2
Vector128<short> a01 = Sse2.Add(v01.AsInt16(), v32.AsInt16()); 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> a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16();
Vector128<short> c0 = Sse2.Add(a01Plus7, a11); Vector128<short> c0 = Sse2.Add(a01Plus7, a11);
Vector128<short> c2 = Sse2.Subtract(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> /// </summary>
private const int MaxCoeffThresh = 31; 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 maxValue;
private int lastNonZero; private int lastNonZero;
@ -73,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vector256<short> v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3); Vector256<short> v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3);
// bin = min(v, MAX_COEFF_THRESH) // bin = min(v, MAX_COEFF_THRESH)
Vector256<short> min0 = Avx2.Min(v0, MaxCoeffThreshVec); Vector256<short> min0 = Avx2.Min(v0, Vector256.Create((short)MaxCoeffThresh));
// Store. // Store.
Unsafe.As<short, Vector256<short>>(ref outputRef) = min0; 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> /// </summary>
internal class Vp8Residual 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 byte[] scratch = new byte[32];
private readonly ushort[] scratchUShort = new ushort[16]; 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> d0 = Avx2.Subtract(Vector256<short>.Zero, c0);
Vector256<short> e0 = Avx2.Max(c0, d0); // abs(v), 16b Vector256<short> e0 = Avx2.Max(c0, d0); // abs(v), 16b
Vector256<sbyte> f = Avx2.PackSignedSaturate(e0, e0); Vector256<sbyte> f = Avx2.PackSignedSaturate(e0, e0);
Vector256<byte> g = Avx2.Min(f.AsByte(), Cst2); Vector256<byte> g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2));
Vector256<byte> h = Avx2.Min(f.AsByte(), Cst67); // clampLevel in [0..67] Vector256<byte> h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67]
ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs); ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs);
ref byte levelsRef = ref MemoryMarshal.GetReference(levels); 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); 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. // UpSample from YUV to RGB.
// Given samples laid out in a square as: // Given samples laid out in a square as:
// [a b] // [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> t1 = Sse2.Or(ad, bc); // (a^d) | (b^c)
Vector128<byte> t2 = Sse2.Or(t1, st); // (a^d) | (b^c) | (s^t) 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> t4 = Sse2.Average(s, t);
Vector128<byte> k = Sse2.Subtract(t4, t3); // k = (a + b + c + d) / 4 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> tmp1 = Sse2.And(ij, st); // (ij) & (s^t)
Vector128<byte> tmp2 = Sse2.Xor(k, input); // (k^in) Vector128<byte> tmp2 = Sse2.Xor(k, input); // (k^in)
Vector128<byte> tmp3 = Sse2.Or(tmp1, tmp2); // ((ij) & (s^t)) | (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 return Sse2.Subtract(tmp0, tmp4); // (k + in + 1) / 2 - lsb_correction
} }
@ -668,9 +625,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ChannelMixing( ChannelMixing(
input0, input0,
input1, input1,
PlanarTo24Shuffle0, Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5), // PlanarTo24Shuffle0
PlanarTo24Shuffle1, Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255), // PlanarTo24Shuffle1
PlanarTo24Shuffle2, 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> r0,
out Vector128<byte> r1, out Vector128<byte> r1,
out Vector128<byte> r2, out Vector128<byte> r2,
@ -683,9 +640,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ChannelMixing( ChannelMixing(
input2, input2,
input3, input3,
PlanarTo24Shuffle3, Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255), // PlanarTo24Shuffle3
PlanarTo24Shuffle4, Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10), // PlanarTo24Shuffle4
PlanarTo24Shuffle5, 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> g0,
out Vector128<byte> g1, out Vector128<byte> g1,
out Vector128<byte> g2, out Vector128<byte> g2,
@ -697,9 +654,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ChannelMixing( ChannelMixing(
input4, input4,
input5, input5,
PlanarTo24Shuffle6, Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255), // PlanarTo24Shuffle6
PlanarTo24Shuffle7, Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255), // PlanarTo24Shuffle7
PlanarTo24Shuffle8, 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> b0,
out Vector128<byte> b1, out Vector128<byte> b1,
out Vector128<byte> b2, out Vector128<byte> b2,
@ -757,21 +714,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
u0 = Sse2.UnpackLow(Vector128<byte>.Zero, u0); u0 = Sse2.UnpackLow(Vector128<byte>.Zero, u0);
v0 = Sse2.UnpackLow(Vector128<byte>.Zero, v0); v0 = Sse2.UnpackLow(Vector128<byte>.Zero, v0);
Vector128<ushort> y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); // These constants are 14b fixed-point version of ITU-R BT.601 constants.
Vector128<ushort> r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); // R = (19077 * y + 26149 * v - 14234) >> 6
Vector128<ushort> g0 = Sse2.MultiplyHigh(u0.AsUInt16(), K6419.AsUInt16()); // G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6
Vector128<ushort> g1 = Sse2.MultiplyHigh(v0.AsUInt16(), K13320.AsUInt16()); // 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> 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> g3 = Sse2.Add(g0, g1);
Vector128<ushort> g4 = Sse2.Subtract(g2, g3); 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> 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. // Use logical shift for B2, which can be larger than 32767.
r = Sse2.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815] 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> /// </summary>
internal static class WebpCommonUtils 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> /// <summary>
/// Checks if the pixel row is not opaque. /// Checks if the pixel row is not opaque.
/// </summary> /// </summary>
@ -41,20 +31,23 @@ namespace SixLabors.ImageSharp.Formats.Webp
int length = (row.Length * 4) - 3; int length = (row.Length * 4) - 3;
fixed (byte* src = rowBytes) 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) for (; i + 128 <= length; i += 128)
{ {
Vector256<byte> a0 = Avx.LoadVector256(src + i).AsByte(); Vector256<byte> a0 = Avx.LoadVector256(src + i).AsByte();
Vector256<byte> a1 = Avx.LoadVector256(src + i + 32).AsByte(); Vector256<byte> a1 = Avx.LoadVector256(src + i + 32).AsByte();
Vector256<byte> a2 = Avx.LoadVector256(src + i + 64).AsByte(); Vector256<byte> a2 = Avx.LoadVector256(src + i + 64).AsByte();
Vector256<byte> a3 = Avx.LoadVector256(src + i + 96).AsByte(); Vector256<byte> a3 = Avx.LoadVector256(src + i + 96).AsByte();
Vector256<int> b0 = Avx2.And(a0, AlphaMaskVector256).AsInt32(); Vector256<int> b0 = Avx2.And(a0, alphaMaskVector256).AsInt32();
Vector256<int> b1 = Avx2.And(a1, AlphaMaskVector256).AsInt32(); Vector256<int> b1 = Avx2.And(a1, alphaMaskVector256).AsInt32();
Vector256<int> b2 = Avx2.And(a2, AlphaMaskVector256).AsInt32(); Vector256<int> b2 = Avx2.And(a2, alphaMaskVector256).AsInt32();
Vector256<int> b3 = Avx2.And(a3, AlphaMaskVector256).AsInt32(); Vector256<int> b3 = Avx2.And(a3, alphaMaskVector256).AsInt32();
Vector256<short> c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); Vector256<short> c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16();
Vector256<short> c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); Vector256<short> c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16();
Vector256<byte> d = Avx2.PackSignedSaturate(c0, c1).AsByte(); 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); int mask = Avx2.MoveMask(bits);
if (mask != -1) if (mask != -1)
{ {
@ -137,18 +130,20 @@ namespace SixLabors.ImageSharp.Formats.Webp
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i) 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> a0 = Sse2.LoadVector128(src + i).AsByte();
Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte(); Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte();
Vector128<byte> a2 = Sse2.LoadVector128(src + i + 32).AsByte(); Vector128<byte> a2 = Sse2.LoadVector128(src + i + 32).AsByte();
Vector128<byte> a3 = Sse2.LoadVector128(src + i + 48).AsByte(); Vector128<byte> a3 = Sse2.LoadVector128(src + i + 48).AsByte();
Vector128<int> b0 = Sse2.And(a0, AlphaMask).AsInt32(); Vector128<int> b0 = Sse2.And(a0, alphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, AlphaMask).AsInt32(); Vector128<int> b1 = Sse2.And(a1, alphaMask).AsInt32();
Vector128<int> b2 = Sse2.And(a2, AlphaMask).AsInt32(); Vector128<int> b2 = Sse2.And(a2, alphaMask).AsInt32();
Vector128<int> b3 = Sse2.And(a3, AlphaMask).AsInt32(); Vector128<int> b3 = Sse2.And(a3, alphaMask).AsInt32();
Vector128<short> c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); Vector128<short> c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16();
Vector128<short> c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); Vector128<short> c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16();
Vector128<byte> d = Sse2.PackSignedSaturate(c0, c1).AsByte(); 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); int mask = Sse2.MoveMask(bits);
if (mask != 0xFFFF) if (mask != 0xFFFF)
{ {
@ -160,13 +155,15 @@ namespace SixLabors.ImageSharp.Formats.Webp
private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i) 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> a0 = Sse2.LoadVector128(src + i).AsByte();
Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte(); Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte();
Vector128<int> b0 = Sse2.And(a0, AlphaMask).AsInt32(); Vector128<int> b0 = Sse2.And(a0, alphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, AlphaMask).AsInt32(); Vector128<int> b1 = Sse2.And(a1, alphaMask).AsInt32();
Vector128<short> c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); Vector128<short> c = Sse2.PackSignedSaturate(b0, b1).AsInt16();
Vector128<byte> d = Sse2.PackSignedSaturate(c, c).AsByte(); 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); int mask = Sse2.MoveMask(bits);
if (mask != 0xFFFF) if (mask != 0xFFFF)
{ {

2
tests/Directory.Build.targets

@ -21,7 +21,7 @@
<PackageReference Update="BenchmarkDotNet" Version="0.13.0" /> <PackageReference Update="BenchmarkDotNet" Version="0.13.0" />
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" Condition="'$(IsWindows)'=='true'" /> <PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" Condition="'$(IsWindows)'=='true'" />
<PackageReference Update="Colourful" Version="3.0.0" /> <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.RemoteExecutor" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" 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" /> <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( [Params(
TestImages.Tiff.CcittFax3AllTermCodes, TestImages.Tiff.CcittFax3AllTermCodes,
TestImages.Tiff.Fax4Compressed2,
TestImages.Tiff.HuffmanRleAllMakeupCodes, TestImages.Tiff.HuffmanRleAllMakeupCodes,
TestImages.Tiff.Calliphora_GrayscaleUncompressed, TestImages.Tiff.Calliphora_GrayscaleUncompressed,
TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor, TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor,

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
@ -9,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
@ -17,11 +19,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Config(typeof(Config.ShortMultiFramework))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeTiff public class EncodeTiff
{ {
private System.Drawing.Image drawing; private Stream stream;
private SDImage drawing;
private Image<Rgba32> core; private Image<Rgba32> core;
private Configuration configuration;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.Calliphora_RgbUncompressed)] [Params(TestImages.Tiff.Calliphora_RgbUncompressed)]
@ -29,9 +30,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Params( [Params(
TiffCompression.None, TiffCompression.None,
TiffCompression.Deflate,
// System.Drawing does not support Deflate or PackBits
// TiffCompression.Deflate,
// TiffCompression.PackBits,
TiffCompression.Lzw, TiffCompression.Lzw,
TiffCompression.PackBits,
TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax,
TiffCompression.Ccitt1D)] TiffCompression.Ccitt1D)]
public TiffCompression Compression { get; set; } public TiffCompression Compression { get; set; }
@ -39,11 +42,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
{ {
if (this.core == null) if (this.stream == null)
{ {
this.configuration = new Configuration(); this.stream = File.OpenRead(this.TestImageFullPath);
this.core = Image.Load<Rgba32>(this.configuration, this.TestImageFullPath); this.core = Image.Load<Rgba32>(this.stream);
this.drawing = System.Drawing.Image.FromFile(this.TestImageFullPath); this.stream.Position = 0;
this.drawing = SDImage.FromStream(this.stream);
} }
} }
@ -70,7 +74,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tiff")] [Benchmark(Description = "ImageSharp Tiff")]
public void TiffCore() 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 }; var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
@ -109,8 +116,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
return EncoderValue.CompressionLZW; return EncoderValue.CompressionLZW;
default: 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 class MultiFramework : Config
{ {
public MultiFramework() => this.AddJob( public MultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472), Job.Default.WithRuntime(CoreRuntime.Core60).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
Job.Default.WithRuntime(CoreRuntime.Core31),
Job.Default.WithRuntime(CoreRuntime.Core50).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
} }
public class ShortMultiFramework : Config public class ShortMultiFramework : Config
{ {
public ShortMultiFramework() => this.AddJob( public ShortMultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), Job.Default.WithRuntime(CoreRuntime.Core60).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
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") }));
} }
public class ShortCore31 : Config 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! // DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\" // 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")] [Trait("Format", "Tiff")]
public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase
{ {
private static readonly Rgba32 Gray000 = new Rgba32(0, 0, 0, 255); private static readonly Rgba32 Gray000 = new(0, 0, 0, 255);
private static readonly Rgba32 Gray128 = new Rgba32(128, 128, 128, 255); private static readonly Rgba32 Gray128 = new(128, 128, 128, 255);
private static readonly Rgba32 Gray255 = new Rgba32(255, 255, 255, 255); private static readonly Rgba32 Gray255 = new(255, 255, 255, 255);
private static readonly Rgba32 Gray0 = new Rgba32(0, 0, 0, 255); private static readonly Rgba32 Gray0 = new(0, 0, 0, 255);
private static readonly Rgba32 Gray8 = new Rgba32(136, 136, 136, 255); private static readonly Rgba32 Gray8 = new(136, 136, 136, 255);
private static readonly Rgba32 GrayF = new Rgba32(255, 255, 255, 255); private static readonly Rgba32 GrayF = new(255, 255, 255, 255);
private static readonly Rgba32 Bit0 = new Rgba32(0, 0, 0, 255); private static readonly Rgba32 Bit0 = new(0, 0, 0, 255);
private static readonly Rgba32 Bit1 = new Rgba32(255, 255, 255, 255); private static readonly Rgba32 Bit1 = new(255, 255, 255, 255);
private static readonly byte[] BilevelBytes4X4 = private static readonly byte[] BilevelBytes4X4 =
{ {
@ -30,8 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
0b10010000 0b10010000
}; };
private static readonly Rgba32[][] BilevelResult4X4 = new[] private static readonly Rgba32[][] BilevelResult4X4 = {
{
new[] { Bit0, Bit1, Bit0, Bit1 }, new[] { Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1 }, new[] { Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, 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] [Theory]
[WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)] [WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush1v1, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] [WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] [WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Contiguoush2v1, 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? // converting the pixel data from Magick.NET to our format with YCbCr?
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
} }
[Theory] [Theory]
@ -612,6 +611,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory] [Theory]
[WithFile(Fax4Compressed, PixelTypes.Rgba32)] [WithFile(Fax4Compressed, PixelTypes.Rgba32)]
[WithFile(Fax4Compressed2, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedMinIsBlack, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)] [WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)]
[WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)] [WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Fax4Compressed<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_Fax4Compressed<TPixel>(TestImageProvider<TPixel> provider)
@ -642,10 +643,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory] [Theory]
[WithFile(RgbJpegCompressed, PixelTypes.Rgba32)] [WithFile(RgbJpegCompressed, PixelTypes.Rgba32)]
[WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)]
[WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)] [WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)]
[WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)] [WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)]
[WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)]
[WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)]
[WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)]
[WithFile(Issues2123, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_JpegCompressed<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_JpegCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, useExactComparer: false); 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] [Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider) 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); 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] [Theory]
[InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] [InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffPhotometricInterpretation.Rgb, 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 Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg";
public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.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 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 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_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff";
public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff";
public const string Fax4Compressed = "Tiff/basi3p02_fax4.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 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 CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff";
public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_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 RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff";
public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff";
public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.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 RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff";
public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff"; public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff";
public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.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 FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff";
public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.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 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 RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff";
public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff"; public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff";
public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.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 FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff";
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.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 Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.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 SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.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 version https://git-lfs.github.com/spec/v1
oid sha256:6b81013d7b0a29ed1ac9c33e175e0c0e69494b93b2b65b692f16d9ea042b9d5d oid sha256:3e889209fc31702aaa7c966c1b5370cc0904cbfbcfd17718977045049cc1bfd9
size 7759 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