Browse Source

Merge branch 'main' into js/decoder-options

pull/2180/head
James Jackson-South 4 years ago
parent
commit
75b0e07794
  1. 17
      src/ImageSharp/Common/Helpers/EnumUtils.cs
  2. 47
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  3. 43
      src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs
  4. 15
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  5. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  6. 159
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs
  7. 147
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  8. 13
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  9. 99
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs
  10. 82
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs
  11. 106
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs
  12. 72
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs
  13. 51
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs
  14. 74
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs
  15. 16
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs
  16. 40
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs
  17. 27
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs
  18. 125
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs
  19. 76
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs
  20. 128
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs
  21. 136
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs
  22. 80
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs
  23. 138
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs
  24. 6
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs
  25. 150
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  26. 2
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs
  27. 131
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs
  28. 52
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs
  29. 43
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs
  30. 51
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs
  31. 39
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs
  32. 34
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs
  33. 38
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs
  34. 26
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs
  35. 74
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs
  36. 50
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs
  37. 74
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs
  38. 82
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs
  39. 43
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs
  40. 75
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs
  41. 61
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs
  42. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  43. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  44. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  45. 116
      src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs
  46. 231
      src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs
  47. 30
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs
  48. 44
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs
  49. 21
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs
  50. 20
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs
  51. 35
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs
  52. 17
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  53. 376
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  54. 205
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
  55. 85
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
  56. 127
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  57. 21
      src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
  58. 165
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  59. 237
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
  60. 259
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  61. 12
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs
  62. 149
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs
  63. 121
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  64. 122
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  65. 61
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  66. 4
      src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs
  67. 15
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  68. 13
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  69. 1
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  70. 38
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  71. 25
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  72. 196
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs
  73. 552
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  74. 15
      src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs
  75. 86
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  76. 2
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
  77. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs
  78. 31
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
  79. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs
  80. 7
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  81. 8
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs
  82. 31
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  83. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
  84. 6
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs
  85. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs
  86. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
  87. 56
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs
  88. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs
  89. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs
  90. 48
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
  91. 240
      tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs
  92. 359
      tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs
  93. 2
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  94. 20
      tests/ImageSharp.Tests.ProfilingSandbox/Program.cs
  95. 97
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs
  96. 57
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  97. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs
  98. 29
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  99. 148
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
  100. 473
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

17
src/ImageSharp/Common/Helpers/EnumUtils.cs

@ -19,15 +19,14 @@ namespace SixLabors.ImageSharp
/// <param name="defaultValue">The default value to return.</param>
/// <returns>The <typeparamref name="TEnum"/>.</returns>
public static TEnum Parse<TEnum>(int value, TEnum defaultValue)
where TEnum : Enum
where TEnum : struct, Enum
{
foreach (TEnum enumValue in Enum.GetValues(typeof(TEnum)))
DebugGuard.IsTrue(Unsafe.SizeOf<TEnum>() == sizeof(int), "Only int-sized enums are supported.");
TEnum valueEnum = Unsafe.As<int, TEnum>(ref value);
if (Enum.IsDefined(valueEnum))
{
TEnum current = enumValue;
if (value == Unsafe.As<TEnum, int>(ref current))
{
return enumValue;
}
return valueEnum;
}
return defaultValue;
@ -41,8 +40,10 @@ namespace SixLabors.ImageSharp
/// <param name="flag">The flag.</param>
/// <returns>The <see cref="bool"/>.</returns>
public static bool HasFlag<TEnum>(TEnum value, TEnum flag)
where TEnum : Enum
where TEnum : struct, Enum
{
DebugGuard.IsTrue(Unsafe.SizeOf<TEnum>() == sizeof(int), "Only int-sized enums are supported.");
uint flagValue = Unsafe.As<TEnum, uint>(ref flag);
return (Unsafe.As<TEnum, uint>(ref value) & flagValue) == flagValue;
}

47
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -21,6 +21,10 @@ namespace SixLabors.ImageSharp
public static ReadOnlySpan<byte> PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 };
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 };
internal static ReadOnlySpan<byte> ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF };
private static ReadOnlySpan<byte> ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 };
private static ReadOnlySpan<byte> ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 };
@ -962,6 +966,49 @@ namespace SixLabors.ImageSharp
blueChannel = blueChannel.Slice(slice);
destination = destination.Slice(slice);
}
internal static void UnpackToRgbPlanesAvx2Reduce(
ref Span<float> redChannel,
ref Span<float> greenChannel,
ref Span<float> blueChannel,
ref ReadOnlySpan<Rgb24> source)
{
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(source));
ref Vector256<float> destRRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(redChannel));
ref Vector256<float> destGRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(greenChannel));
ref Vector256<float> destBRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(blueChannel));
Vector256<uint> extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
Vector256<byte> extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
const int bytesPerRgbStride = 24;
int count = (int)((uint)source.Length / 8);
for (int i = 0; i < count; i++)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, Vector256<byte>.Zero);
bx = Avx2.UnpackHigh(rgb, Vector256<byte>.Zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256<byte>.Zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256<byte>.Zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256<byte>.Zero).AsInt32());
Unsafe.Add(ref destRRef, i) = r;
Unsafe.Add(ref destGRef, i) = g;
Unsafe.Add(ref destBRef, i) = b;
}
int sliceCount = count * 8;
redChannel = redChannel.Slice(sliceCount);
greenChannel = greenChannel.Slice(sliceCount);
blueChannel = blueChannel.Slice(sliceCount);
source = source.Slice(sliceCount);
}
}
}
}

43
src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs

@ -65,6 +65,25 @@ namespace SixLabors.ImageSharp
PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination);
}
[MethodImpl(InliningOptions.ShortMethod)]
internal static void UnpackToRgbPlanes(
Span<float> redChannel,
Span<float> greenChannel,
Span<float> blueChannel,
ReadOnlySpan<Rgb24> source)
{
DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!");
DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!");
DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!");
if (Avx2.IsSupported)
{
HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source);
}
UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source);
}
private static void PackFromRgbPlanesScalarBatchedReduce(
ref ReadOnlySpan<byte> redChannel,
ref ReadOnlySpan<byte> greenChannel,
@ -200,5 +219,29 @@ namespace SixLabors.ImageSharp
d.A = 255;
}
}
private static void UnpackToRgbPlanesScalar(
Span<float> redChannel,
Span<float> greenChannel,
Span<float> blueChannel,
ReadOnlySpan<Rgb24> source)
{
DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!");
DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!");
DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!");
ref float r = ref MemoryMarshal.GetReference(redChannel);
ref float g = ref MemoryMarshal.GetReference(greenChannel);
ref float b = ref MemoryMarshal.GetReference(blueChannel);
ref Rgb24 rgb = ref MemoryMarshal.GetReference(source);
for (int i = 0; i < source.Length; i++)
{
ref Rgb24 src = ref Unsafe.Add(ref rgb, i);
Unsafe.Add(ref r, i) = src.R;
Unsafe.Add(ref g, i) = src.G;
Unsafe.Add(ref b, i) = src.B;
}
}
}
}

15
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -122,6 +122,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
public static Block8x8 Load(ReadOnlySpan<byte> data)
{
Unsafe.SkipInit(out Block8x8 result);
result.LoadFrom(data);
return result;
}
public void LoadFrom(ReadOnlySpan<byte> source)
{
for (int i = 0; i < Size; i++)
{
this[i] = source[i];
}
}
/// <summary>
/// Load raw 16bit integers from source.
/// </summary>

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
internal partial struct Block8x8F
{
/// <summary>
/// Level shift by +maximum/2, clip to [0, maximum]

159
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs

@ -0,0 +1,159 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
// ReSharper disable UseObjectOrCollectionInitializer
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyFrom(ref float areaOrigin, int areaStride) =>
CopyFrom1x1Scale(ref Unsafe.As<float, byte>(ref areaOrigin), ref Unsafe.As<Block8x8F, byte>(ref this), areaStride);
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
CopyTo1x1Scale(ref Unsafe.As<Block8x8F, byte>(ref this), ref Unsafe.As<float, byte>(ref areaOrigin), areaStride);
return;
}
if (horizontalScale == 2 && verticalScale == 2)
{
this.CopyTo2x2Scale(ref areaOrigin, areaStride);
return;
}
// TODO: Optimize: implement all cases with scale-specific, loopless code!
this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale);
}
private void CopyTo2x2Scale(ref float areaOrigin, int areaStride)
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref areaOrigin);
int destStride = (int)((uint)areaStride / 2);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride);
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nint row, nint destStride)
{
ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row);
ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1);
nint offset = 2 * row * destStride;
ref Vector4 dTopLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset));
ref Vector4 dBottomLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset + destStride));
var xyLeft = new Vector4(sLeft.X);
xyLeft.Z = sLeft.Y;
xyLeft.W = sLeft.Y;
var zwLeft = new Vector4(sLeft.Z);
zwLeft.Z = sLeft.W;
zwLeft.W = sLeft.W;
var xyRight = new Vector4(sRight.X);
xyRight.Z = sRight.Y;
xyRight.W = sRight.Y;
var zwRight = new Vector4(sRight.Z);
zwRight.Z = sRight.W;
zwRight.W = sRight.W;
dTopLeft = xyLeft;
Unsafe.Add(ref dTopLeft, 1) = zwLeft;
Unsafe.Add(ref dTopLeft, 2) = xyRight;
Unsafe.Add(ref dTopLeft, 3) = zwRight;
dBottomLeft = xyLeft;
Unsafe.Add(ref dBottomLeft, 1) = zwLeft;
Unsafe.Add(ref dBottomLeft, 2) = xyRight;
Unsafe.Add(ref dBottomLeft, 3) = zwRight;
}
}
[MethodImpl(InliningOptions.ColdPath)]
private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[y8 + x];
nint baseIdx = (yy * areaStride) + xx;
for (nint i = 0; i < verticalScale; i++, baseIdx += areaStride)
{
for (nint j = 0; j < horizontalScale; j++)
{
// area[xx + j, yy + i] = value;
Unsafe.Add(ref areaOrigin, baseIdx + j) = value;
}
}
}
}
}
private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride)
{
int destStride = areaStride * sizeof(float);
CopyRowImpl(ref origin, ref dest, destStride, 0);
CopyRowImpl(ref origin, ref dest, destStride, 1);
CopyRowImpl(ref origin, ref dest, destStride, 2);
CopyRowImpl(ref origin, ref dest, destStride, 3);
CopyRowImpl(ref origin, ref dest, destStride, 4);
CopyRowImpl(ref origin, ref dest, destStride, 5);
CopyRowImpl(ref origin, ref dest, destStride, 6);
CopyRowImpl(ref origin, ref dest, destStride, 7);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row)
{
origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float));
dest = ref Unsafe.Add(ref dest, row * destStride);
Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float));
}
}
private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride)
{
int destStride = areaStride * sizeof(float);
CopyRowImpl(ref origin, ref dest, destStride, 0);
CopyRowImpl(ref origin, ref dest, destStride, 1);
CopyRowImpl(ref origin, ref dest, destStride, 2);
CopyRowImpl(ref origin, ref dest, destStride, 3);
CopyRowImpl(ref origin, ref dest, destStride, 4);
CopyRowImpl(ref origin, ref dest, destStride, 5);
CopyRowImpl(ref origin, ref dest, destStride, 6);
CopyRowImpl(ref origin, ref dest, destStride, 7);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row)
{
origin = ref Unsafe.Add(ref origin, row * sourceStride);
dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float));
Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float));
}
}
}
}

147
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs

@ -1,147 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable UseObjectOrCollectionInitializer
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
/// <summary>
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(in Buffer2DRegion<float> region, int horizontalScale, int verticalScale)
{
ref float areaOrigin = ref region.GetReferenceToOrigin();
this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
this.Copy1x1Scale(ref areaOrigin, areaStride);
return;
}
if (horizontalScale == 2 && verticalScale == 2)
{
this.Copy2x2Scale(ref areaOrigin, areaStride);
return;
}
// TODO: Optimize: implement all cases with scale-specific, loopless code!
this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale);
}
public void Copy1x1Scale(ref float areaOrigin, int areaStride)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref areaOrigin);
int destStride = areaStride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
{
ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
private void Copy2x2Scale(ref float areaOrigin, int areaStride)
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref areaOrigin);
int destStride = areaStride / 2;
this.WidenCopyRowImpl2x2(ref destBase, 0, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 1, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 2, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 3, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 4, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 5, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 6, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 7, destStride);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride)
{
ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1);
int offset = 2 * row * destStride;
ref Vector4 dTopLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset));
ref Vector4 dBottomLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset + destStride));
var xyLeft = new Vector4(sLeft.X);
xyLeft.Z = sLeft.Y;
xyLeft.W = sLeft.Y;
var zwLeft = new Vector4(sLeft.Z);
zwLeft.Z = sLeft.W;
zwLeft.W = sLeft.W;
var xyRight = new Vector4(sRight.X);
xyRight.Z = sRight.Y;
xyRight.W = sRight.Y;
var zwRight = new Vector4(sRight.Z);
zwRight.Z = sRight.W;
zwRight.W = sRight.W;
dTopLeft = xyLeft;
Unsafe.Add(ref dTopLeft, 1) = zwLeft;
Unsafe.Add(ref dTopLeft, 2) = xyRight;
Unsafe.Add(ref dTopLeft, 3) = zwRight;
dBottomLeft = xyLeft;
Unsafe.Add(ref dBottomLeft, 1) = zwLeft;
Unsafe.Add(ref dBottomLeft, 2) = xyRight;
Unsafe.Add(ref dBottomLeft, 3) = zwRight;
}
[MethodImpl(InliningOptions.ColdPath)]
private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[y8 + x];
for (int i = 0; i < verticalScale; i++)
{
int baseIdx = ((yy + i) * areaStride) + xx;
for (int j = 0; j < horizontalScale; j++)
{
// area[xx + j, yy + i] = value;
Unsafe.Add(ref areaOrigin, baseIdx + j) = value;
}
}
}
}
}
}
}

13
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -352,6 +352,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
public void DE_NormalizeColors(float maximum)
{
if (SimdUtils.HasVector8)
{
this.NormalizeColorsAndRoundInPlaceVector8(maximum);
}
else
{
this.NormalizeColorsInPlace(maximum);
this.RoundInPlace();
}
}
/// <summary>
/// Rounds all values in the block.
/// </summary>

99
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs

@ -0,0 +1,99 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykAvx : JpegColorConverterAvx
{
public CmykAvx(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Avx.Multiply(k, scale);
c = Avx.Multiply(c, k);
m = Avx.Multiply(m, k);
y = Avx.Multiply(y, k);
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destC =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destM =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> destK =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> srcR =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcG =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcB =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
var scale = Vector256.Create(maxValue);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector256<float> ctmp = Avx.Subtract(scale, Unsafe.Add(ref srcR, i));
Vector256<float> mtmp = Avx.Subtract(scale, Unsafe.Add(ref srcG, i));
Vector256<float> ytmp = Avx.Subtract(scale, Unsafe.Add(ref srcB, i));
Vector256<float> ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp));
Vector256<float> kMask = Avx.CompareNotEqual(ktmp, scale);
ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(scale, ktmp)), kMask);
mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(scale, ktmp)), kMask);
ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(scale, ktmp)), kMask);
Unsafe.Add(ref destC, i) = Avx.Subtract(scale, Avx.Multiply(ctmp, scale));
Unsafe.Add(ref destM, i) = Avx.Subtract(scale, Avx.Multiply(mtmp, scale));
Unsafe.Add(ref destY, i) = Avx.Subtract(scale, Avx.Multiply(ytmp, scale));
Unsafe.Add(ref destK, i) = Avx.Subtract(scale, ktmp);
}
}
}
}
}
#endif

82
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs

@ -0,0 +1,82 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykScalar : JpegColorConverterScalar
{
public CmykScalar(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertToRgbInplace(values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertFromRgb(values, this.MaximumValue, r, g, b);
public static void ConvertToRgbInplace(in ComponentValues values, float maxValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < c0.Length; i++)
{
float c = c0[i];
float m = c1[i];
float y = c2[i];
float k = c3[i];
k *= scale;
c0[i] = c * k;
c1[i] = m * k;
c2[i] = y * k;
}
}
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> r, Span<float> g, Span<float> b)
{
Span<float> c = values.Component0;
Span<float> m = values.Component1;
Span<float> y = values.Component2;
Span<float> k = values.Component3;
for (int i = 0; i < c.Length; i++)
{
float ctmp = 255f - r[i];
float mtmp = 255f - g[i];
float ytmp = 255f - b[i];
float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp);
if (ktmp >= 255f)
{
ctmp = 0f;
mtmp = 0f;
ytmp = 0f;
}
else
{
ctmp = (ctmp - ktmp) / (255f - ktmp);
mtmp = (mtmp - ktmp) / (255f - ktmp);
ytmp = (ytmp - ktmp) / (255f - ktmp);
}
c[i] = maxValue - (ctmp * maxValue);
m[i] = maxValue - (mtmp * maxValue);
y[i] = maxValue - (ytmp * maxValue);
k[i] = maxValue - ktmp;
}
}
}
}
}

106
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs

@ -0,0 +1,106 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class CmykVector : JpegColorConverterVector
{
public CmykVector(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> mBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c = ref Unsafe.Add(ref cBase, i);
ref Vector<float> m = ref Unsafe.Add(ref mBase, i);
ref Vector<float> y = ref Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i);
k *= scale;
c *= k;
m *= k;
y *= k;
}
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
=> CmykScalar.ConvertToRgbInplace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b);
public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span<float> r, Span<float> g, Span<float> b)
{
ref Vector<float> destC =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> destM =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> destY =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> destK =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector<float> srcR =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(r));
ref Vector<float> srcG =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(g));
ref Vector<float> srcB =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(b));
// Used for the color conversion
var scale = new Vector<float>(maxValue);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector<float> ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp));
var kMask = Vector.Equals(ktmp, scale);
ctmp = Vector.AndNot((ctmp - ktmp) / (scale - ktmp), kMask.As<int, float>());
mtmp = Vector.AndNot((mtmp - ktmp) / (scale - ktmp), kMask.As<int, float>());
ytmp = Vector.AndNot((ytmp - ktmp) / (scale - ktmp), kMask.As<int, float>());
Unsafe.Add(ref destC, i) = scale - (ctmp * scale);
Unsafe.Add(ref destM, i) = scale - (mtmp * scale);
Unsafe.Add(ref destY, i) = scale - (ytmp * scale);
Unsafe.Add(ref destK, i) = scale - ktmp;
}
}
public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span<float> r, Span<float> g, Span<float> b)
=> CmykScalar.ConvertFromRgb(values, maxValue, r, g, b);
}
}
}

72
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayscaleAvx : JpegColorConverterAvx
{
public GrayscaleAvx(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = Avx.Multiply(c0, scale);
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destLuminance =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> srcRed =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcGreen =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcBlue =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
// Used for the color conversion
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> r = ref Unsafe.Add(ref srcRed, i);
ref Vector256<float> g = ref Unsafe.Add(ref srcGreen, i);
ref Vector256<float> b = ref Unsafe.Add(ref srcBlue, i);
// luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b)
Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
}
}
}
}
}
#endif

51
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayscaleScalar : JpegColorConverterScalar
{
public GrayscaleScalar(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertToRgbInplace(values.Component0, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertCoreInplaceFromRgb(values, r, g, b);
internal static void ConvertToRgbInplace(Span<float> values, float maxValue)
{
ref float valuesRef = ref MemoryMarshal.GetReference(values);
float scale = 1 / maxValue;
for (nint i = 0; i < values.Length; i++)
{
Unsafe.Add(ref valuesRef, i) *= scale;
}
}
internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> c0 = values.Component0;
for (int i = 0; i < c0.Length; i++)
{
// luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b)
float luma = (0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]);
c0[i] = luma;
}
}
}
}
}

74
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayScaleVector : JpegColorConverterVector
{
public GrayScaleVector(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
var scale = new Vector<float>(1 / this.MaximumValue);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c0 = ref Unsafe.Add(ref cBase, i);
c0 *= scale;
}
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
=> GrayscaleScalar.ConvertToRgbInplace(values.Component0, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector<float> destLuma =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> srcR =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector<float> srcG =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector<float> srcB =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(bLane));
var rMult = new Vector<float>(0.299f);
var gMult = new Vector<float>(0.587f);
var bMult = new Vector<float>(0.114f);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector<float> r = Unsafe.Add(ref srcR, i);
Vector<float> g = Unsafe.Add(ref srcR, i);
Vector<float> b = Unsafe.Add(ref srcR, i);
// luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b)
Unsafe.Add(ref destLuma, i) = (rMult * r) + (gMult * g) + (bMult * b);
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b);
}
}
}

16
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs

@ -2,22 +2,24 @@
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromRgbAvx : JpegColorConverterAvx
internal sealed class RgbAvx : JpegColorConverterAvx
{
public FromRgbAvx(int precision)
public RgbAvx(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> rBase =
@ -40,6 +42,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
b = Avx.Multiply(b, scale);
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
rLane.CopyTo(values.Component0);
gLane.CopyTo(values.Component1);
bLane.CopyTo(values.Component2);
}
}
}
}

40
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class RgbScalar : JpegColorConverterScalar
{
public RgbScalar(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertToRgbInplace(values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertFromRgb(values, r, g, b);
internal static void ConvertToRgbInplace(ComponentValues values, float maxValue)
{
GrayscaleScalar.ConvertToRgbInplace(values.Component0, maxValue);
GrayscaleScalar.ConvertToRgbInplace(values.Component1, maxValue);
GrayscaleScalar.ConvertToRgbInplace(values.Component2, maxValue);
}
internal static void ConvertFromRgb(ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
{
r.CopyTo(values.Component0);
g.CopyTo(values.Component1);
b.CopyTo(values.Component2);
}
}
}
}

27
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs

@ -1,22 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromRgbVector : JpegColorConverterVector
internal sealed class RgbVector : JpegColorConverterVector
{
public FromRgbVector(int precision)
public RgbVector(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
{
ref Vector<float> rBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -39,8 +41,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
=> RgbScalar.ConvertToRgbInplace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
{
r.CopyTo(values.Component0);
g.CopyTo(values.Component1);
b.CopyTo(values.Component2);
}
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> RgbScalar.ConvertFromRgb(values, r, g, b);
}
}
}

125
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs

@ -0,0 +1,125 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrAvx : JpegColorConverterAvx
{
public YCbCrAvx(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue);
var rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = c0;
Vector256<float> cb = Avx.Add(c1, chromaOffset);
Vector256<float> cr = Avx.Add(c2, chromaOffset);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destCb =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destCr =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> srcR =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcG =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcB =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
// Used for the color conversion
var chromaOffset = Vector256.Create(this.HalfValue);
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector256<float> r = Unsafe.Add(ref srcR, i);
Vector256<float> g = Unsafe.Add(ref srcG, i);
Vector256<float> b = Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector256<float> y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
Vector256<float> cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
Vector256<float> cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}
}
#endif

76
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs

@ -0,0 +1,76 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrScalar : JpegColorConverterScalar
{
// derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public YCbCrScalar(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertFromRgb(values, this.HalfValue, r, g, b);
public static void ConvertToRgbInplace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale;
}
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> y = values.Component0;
Span<float> cb = values.Component1;
Span<float> cr = values.Component2;
for (int i = 0; i < y.Length; i++)
{
float r = rLane[i];
float g = gLane[i];
float b = bLane[i];
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b);
cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b);
cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b);
}
}
}
}
}

128
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs

@ -0,0 +1,128 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class YCbCrVector : JpegColorConverterVector
{
public YCbCrVector(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
{
ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / this.MaximumValue);
var rCrMult = new Vector<float>(YCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-YCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-YCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(YCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> y = Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref c1Base, i) + chromaOffset;
Vector<float> cr = Unsafe.Add(ref c2Base, i) + chromaOffset;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector<float> r = y + (cr * rCrMult);
Vector<float> g = y + (cb * gCbMult) + (cr * gCrMult);
Vector<float> b = y + (cb * bCbMult);
r = r.FastRound();
g = g.FastRound();
b = b.FastRound();
r *= scale;
g *= scale;
b *= scale;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
=> YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector<float> destY =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> destCb =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> destCr =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> srcR =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector<float> srcG =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector<float> srcB =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(bLane));
var chromaOffset = new Vector<float>(this.HalfValue);
var rYMult = new Vector<float>(0.299f);
var gYMult = new Vector<float>(0.587f);
var bYMult = new Vector<float>(0.114f);
var rCbMult = new Vector<float>(0.168736f);
var gCbMult = new Vector<float>(0.331264f);
var bCbMult = new Vector<float>(0.5f);
var rCrMult = new Vector<float>(0.5f);
var gCrMult = new Vector<float>(0.418688f);
var bCrMult = new Vector<float>(0.081312f);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector<float> r = Unsafe.Add(ref srcR, i);
Vector<float> g = Unsafe.Add(ref srcG, i);
Vector<float> b = Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b);
Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b);
Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b);
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> YCbCrScalar.ConvertFromRgb(values, this.HalfValue, r, g, b);
}
}
}

136
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs

@ -0,0 +1,136 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKAvx : JpegColorConverterAvx
{
public YccKAvx(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = c0;
Vector256<float> cb = Avx.Add(c1, chromaOffset);
Vector256<float> cr = Avx.Add(c2, chromaOffset);
Vector256<float> scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g =
HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = Avx.Subtract(max, Avx.RoundToNearestInteger(r));
g = Avx.Subtract(max, Avx.RoundToNearestInteger(g));
b = Avx.Subtract(max, Avx.RoundToNearestInteger(b));
r = Avx.Multiply(r, scaledK);
g = Avx.Multiply(g, scaledK);
b = Avx.Multiply(b, scaledK);
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykAvx.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destCb =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destCr =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> srcR = ref destY;
ref Vector256<float> srcG = ref destCb;
ref Vector256<float> srcB = ref destCr;
// Used for the color conversion
var maxSampleValue = Vector256.Create(this.MaximumValue);
var chromaOffset = Vector256.Create(this.HalfValue);
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector256<float> r = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i));
Vector256<float> g = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i));
Vector256<float> b = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i));
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector256<float> y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
Vector256<float> cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
Vector256<float> cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
Unsafe.Add(ref destY, i) = y;
Unsafe.Add(ref destCb, i) = cb;
Unsafe.Add(ref destCr, i) = cr;
}
}
}
}
}
#endif

80
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs

@ -0,0 +1,80 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKScalar : JpegColorConverterScalar
{
// derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public YccKScalar(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b);
public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < values.Component0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
float scaledK = c3[i] * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK;
c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK;
}
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane);
// cmyk -> ycck
Span<float> c = values.Component0;
Span<float> m = values.Component1;
Span<float> y = values.Component2;
for (int i = 0; i < y.Length; i++)
{
float r = maxValue - c[i];
float g = maxValue - m[i];
float b = maxValue - y[i];
// k value is passed untouched from rgb -> cmyk conversion
c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b);
m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b);
y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b);
}
}
}
}
}

138
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs

@ -0,0 +1,138 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKVector : JpegColorConverterVector
{
public YccKVector(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceVectorized(in ComponentValues values)
{
ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
var max = new Vector<float>(this.MaximumValue);
var rCrMult = new Vector<float>(YCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-YCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-YCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(YCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> y = c0;
Vector<float> cb = c1 + chromaOffset;
Vector<float> cr = c2 + chromaOffset;
Vector<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector<float> r = y + (cr * rCrMult);
Vector<float> g = y + (cb * gCbMult) + (cr * gCrMult);
Vector<float> b = y + (cb * bCbMult);
r = (max - r.FastRound()) * scaledK;
g = (max - g.FastRound()) * scaledK;
b = (max - b.FastRound()) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values)
=> YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
ref Vector<float> destY =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> destCb =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> destCr =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> srcR = ref destY;
ref Vector<float> srcG = ref destCb;
ref Vector<float> srcB = ref destCr;
var maxSampleValue = new Vector<float>(this.MaximumValue);
var chromaOffset = new Vector<float>(this.HalfValue);
var rYMult = new Vector<float>(0.299f);
var gYMult = new Vector<float>(0.587f);
var bYMult = new Vector<float>(0.114f);
var rCbMult = new Vector<float>(0.168736f);
var gCbMult = new Vector<float>(0.331264f);
var bCbMult = new Vector<float>(0.5f);
var rCrMult = new Vector<float>(0.5f);
var gCrMult = new Vector<float>(0.418688f);
var bCrMult = new Vector<float>(0.081312f);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
Vector<float> r = maxSampleValue - Unsafe.Add(ref srcR, i);
Vector<float> g = maxSampleValue - Unsafe.Add(ref srcG, i);
Vector<float> b = maxSampleValue - Unsafe.Add(ref srcB, i);
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b);
Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b);
Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b);
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
{
// rgb -> cmyk
CmykScalar.ConvertFromRgb(in values, this.MaximumValue, r, g, b);
// cmyk -> ycck
YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b);
}
}
}
}

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs

@ -4,7 +4,7 @@
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
@ -26,7 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public sealed override bool IsAvailable => Avx.IsSupported;
public static bool IsSupported => Avx.IsSupported;
public sealed override bool IsAvailable => IsSupported;
public sealed override int ElementsPerBatch => Vector256<float>.Count;
}

150
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -3,10 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Encapsulates the conversion of color channels from jpeg image to RGB channels.
@ -88,88 +87,130 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
public abstract void ConvertToRgbInplace(in ComponentValues values);
/// <summary>
/// Converts RGB lanes to jpeg component values.
/// </summary>
/// <param name="values">Jpeg component values.</param>
/// <param name="rLane">Red colors lane.</param>
/// <param name="gLane">Green colors lane.</param>
/// <param name="bLane">Blue colors lane.</param>
public abstract void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane);
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for all supported colorspaces and precisions.
/// </summary>
private static JpegColorConverterBase[] CreateConverters()
{
var converters = new List<JpegColorConverterBase>();
// 5 color types with 2 supported precisions: 8 bit & 12 bit
const int colorConvertersCount = 5 * 2;
var converters = new JpegColorConverterBase[colorConvertersCount];
// 8-bit converters
converters.AddRange(GetYCbCrConverters(8));
converters.AddRange(GetYccKConverters(8));
converters.AddRange(GetCmykConverters(8));
converters.AddRange(GetGrayScaleConverters(8));
converters.AddRange(GetRgbConverters(8));
converters[0] = GetYCbCrConverter(8);
converters[1] = GetYccKConverter(8);
converters[2] = GetCmykConverter(8);
converters[3] = GetGrayScaleConverter(8);
converters[4] = GetRgbConverter(8);
// 12-bit converters
converters.AddRange(GetYCbCrConverters(12));
converters.AddRange(GetYccKConverters(12));
converters.AddRange(GetCmykConverters(12));
converters.AddRange(GetGrayScaleConverters(12));
converters.AddRange(GetRgbConverters(12));
converters[5] = GetYCbCrConverter(12);
converters[6] = GetYccKConverter(12);
converters[7] = GetCmykConverter(12);
converters[8] = GetGrayScaleConverter(12);
converters[9] = GetRgbConverter(12);
return converters.Where(x => x.IsAvailable).ToArray();
return converters;
}
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for the YCbCr colorspace.
/// </summary>
private static IEnumerable<JpegColorConverterBase> GetYCbCrConverters(int precision)
private static JpegColorConverterBase GetYCbCrConverter(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromYCbCrAvx(precision);
#endif
yield return new FromYCbCrVector(precision);
yield return new FromYCbCrScalar(precision);
if (JpegColorConverterAvx.IsSupported)
{
return new YCbCrAvx(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new YCbCrVector(precision);
}
return new YCbCrScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for the YccK colorspace.
/// </summary>
private static IEnumerable<JpegColorConverterBase> GetYccKConverters(int precision)
private static JpegColorConverterBase GetYccKConverter(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromYccKAvx(precision);
#endif
yield return new FromYccKVector(precision);
yield return new FromYccKScalar(precision);
if (JpegColorConverterAvx.IsSupported)
{
return new YccKAvx(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new YccKVector(precision);
}
return new YccKScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for the CMYK colorspace.
/// </summary>
private static IEnumerable<JpegColorConverterBase> GetCmykConverters(int precision)
private static JpegColorConverterBase GetCmykConverter(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromCmykAvx(precision);
#endif
yield return new FromCmykVector(precision);
yield return new FromCmykScalar(precision);
if (JpegColorConverterAvx.IsSupported)
{
return new CmykAvx(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new CmykVector(precision);
}
return new CmykScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for the gray scale colorspace.
/// </summary>
private static IEnumerable<JpegColorConverterBase> GetGrayScaleConverters(int precision)
private static JpegColorConverterBase GetGrayScaleConverter(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromGrayscaleAvx(precision);
#endif
yield return new FromGrayScaleVector(precision);
yield return new FromGrayscaleScalar(precision);
if (JpegColorConverterAvx.IsSupported)
{
return new GrayscaleAvx(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new GrayScaleVector(precision);
}
return new GrayscaleScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for the RGB colorspace.
/// </summary>
private static IEnumerable<JpegColorConverterBase> GetRgbConverters(int precision)
private static JpegColorConverterBase GetRgbConverter(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromRgbAvx(precision);
#endif
yield return new FromRgbVector(precision);
yield return new FromRgbScalar(precision);
if (JpegColorConverterAvx.IsSupported)
{
return new RgbAvx(precision);
}
if (JpegColorConverterVector.IsSupported)
{
return new RgbScalar(precision);
}
return new GrayscaleScalar(precision);
}
/// <summary>
@ -228,7 +269,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// </summary>
/// <param name="processors">List of component color processors.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<ComponentProcessor> processors, int row)
public ComponentValues(IReadOnlyList<Decoder.ComponentProcessor> processors, int row)
{
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
this.ComponentCount = processors.Count;
this.Component0 = processors[0].GetColorBufferRowSpan(row);
// In case of grayscale, Component1 and Component2 point to Component0 memory area
this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span<float>.Empty;
}
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
/// </summary>
/// <param name="processors">List of component color processors.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<Encoder.ComponentProcessor> processors, int row)
{
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{

131
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs

@ -0,0 +1,131 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector"/> API.
/// </summary>
/// <remarks>
/// Converters of this family can work with data of any size.
/// Even though real life data is guaranteed to be of size
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
/// such data out of the box. These converters have fallback code
/// for 'remainder' data.
/// </remarks>
internal abstract class JpegColorConverterVector : JpegColorConverterBase
{
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
/// <summary>
/// Gets a value indicating whether this converter is supported on current hardware.
/// </summary>
public static bool IsSupported => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0;
/// <inheritdoc/>
public sealed override bool IsAvailable => IsSupported;
public override int ElementsPerBatch => Vector<float>.Count;
/// <inheritdoc/>
public sealed override void ConvertToRgbInplace(in ComponentValues values)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
int length = values.Component0.Length;
int remainder = (int)((uint)length % (uint)Vector<float>.Count);
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertToRgbInplaceVectorized(values.Slice(0, simdCount));
}
// Jpeg images width is always divisible by 8 without a remainder
// so it's safe to say SSE/AVX1/AVX2 implementations would never have
// 'remainder' pixels
// But some exotic simd implementations e.g. AVX-512 can have
// remainder pixels
if (remainder > 0)
{
this.ConvertToRgbInplaceScalarRemainder(values.Slice(simdCount, remainder));
}
}
/// <inheritdoc/>
public sealed override void ConvertFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
int length = values.Component0.Length;
int remainder = (int)((uint)length % (uint)Vector<float>.Count);
int simdCount = length - remainder;
if (simdCount > 0)
{
this.ConvertFromRgbVectorized(
values.Slice(0, simdCount),
r.Slice(0, simdCount),
g.Slice(0, simdCount),
b.Slice(0, simdCount));
}
// Jpeg images width is always divisible by 8 without a remainder
// so it's safe to say SSE/AVX1/AVX2 implementations would never have
// 'remainder' pixels
// But some exotic simd implementations e.g. AVX-512 can have
// remainder pixels
if (remainder > 0)
{
this.ConvertFromRgbScalarRemainder(
values.Slice(simdCount, remainder),
r.Slice(simdCount, remainder),
g.Slice(simdCount, remainder),
b.Slice(simdCount, remainder));
}
}
/// <summary>
/// Converts planar jpeg component values in <paramref name="values"/>
/// to RGB color space inplace using <see cref="Vector"/> API.
/// </summary>
/// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInplaceVectorized(in ComponentValues values);
/// <summary>
/// Converts remainder of the planar jpeg component values after
/// conversion in <see cref="ConvertToRgbInplaceVectorized(in ComponentValues)"/>.
/// </summary>
/// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
protected abstract void ConvertToRgbInplaceScalarRemainder(in ComponentValues values);
/// <summary>
/// Converts RGB lanes to jpeg component values using <see cref="Vector"/> API.
/// </summary>
/// <param name="values">Jpeg component values.</param>
/// <param name="rLane">Red colors lane.</param>
/// <param name="gLane">Green colors lane.</param>
/// <param name="bLane">Blue colors lane.</param>
protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane);
/// <summary>
/// Converts remainder of RGB lanes to jpeg component values after
/// conversion in <see cref="ConvertFromRgbVectorized(in ComponentValues, Span{float}, Span{float}, Span{float})"/>.
/// </summary>
/// <param name="values">Jpeg component values.</param>
/// <param name="rLane">Red colors lane.</param>
/// <param name="gLane">Green colors lane.</param>
/// <param name="bLane">Blue colors lane.</param>
protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane);
}
}
}

52
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs

@ -1,52 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromCmykAvx : JpegColorConverterAvx
{
public FromCmykAvx(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Avx.Multiply(k, scale);
c = Avx.Multiply(c, k);
m = Avx.Multiply(m, k);
y = Avx.Multiply(y, k);
}
}
}
}
}
#endif

43
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs

@ -1,43 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromCmykScalar : JpegColorConverterScalar
{
public FromCmykScalar(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertCoreInplace(values, this.MaximumValue);
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < c0.Length; i++)
{
float c = c0[i];
float m = c1[i];
float y = c2[i];
float k = c3[i];
k *= scale;
c0[i] = c * k;
c1[i] = m * k;
c2[i] = y * k;
}
}
}
}
}

51
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs

@ -1,51 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromCmykVector : JpegColorConverterVector
{
public FromCmykVector(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> mBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c = ref Unsafe.Add(ref cBase, i);
ref Vector<float> m = ref Unsafe.Add(ref mBase, i);
ref Vector<float> y = ref Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i);
k *= scale;
c *= k;
m *= k;
y *= k;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

39
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs

@ -1,39 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromGrayscaleAvx : JpegColorConverterAvx
{
public FromGrayscaleAvx(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = Avx.Multiply(c0, scale);
}
}
}
}
}
#endif

34
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromGrayscaleScalar : JpegColorConverterScalar
{
public FromGrayscaleScalar(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertCoreInplace(values.Component0, this.MaximumValue);
internal static void ConvertCoreInplace(Span<float> values, float maxValue)
{
ref float valuesRef = ref MemoryMarshal.GetReference(values);
float scale = 1 / maxValue;
for (nint i = 0; i < values.Length; i++)
{
Unsafe.Add(ref valuesRef, i) *= scale;
}
}
}
}
}

38
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs

@ -1,38 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromGrayScaleVector : JpegColorConverterVector
{
public FromGrayScaleVector(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
var scale = new Vector<float>(1 / this.MaximumValue);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c0 = ref Unsafe.Add(ref cBase, i);
c0 *= scale;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue);
}
}
}

26
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs

@ -1,26 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromRgbScalar : JpegColorConverterScalar
{
public FromRgbScalar(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertCoreInplace(values, this.MaximumValue);
internal static void ConvertCoreInplace(ComponentValues values, float maxValue)
{
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue);
FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue);
FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue);
}
}
}
}

74
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs

@ -1,74 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYCbCrAvx : JpegColorConverterAvx
{
public FromYCbCrAvx(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = c0;
Vector256<float> cb = Avx.Add(c1, chromaOffset);
Vector256<float> cr = Avx.Add(c2, chromaOffset);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
c0 = r;
c1 = g;
c2 = b;
}
}
}
}
}
#endif

50
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs

@ -1,50 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYCbCrScalar : JpegColorConverterScalar
{
// TODO: comments, derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public FromYCbCrScalar(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale;
}
}
}
}
}

74
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs

@ -1,74 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYCbCrVector : JpegColorConverterVector
{
public FromYCbCrVector(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> y = Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref c1Base, i) + chromaOffset;
Vector<float> cr = Unsafe.Add(ref c2Base, i) + chromaOffset;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector<float> r = y + (cr * rCrMult);
Vector<float> g = y + (cb * gCbMult) + (cr * gCrMult);
Vector<float> b = y + (cb * bCbMult);
r = r.FastRound();
g = g.FastRound();
b = b.FastRound();
r *= scale;
g *= scale;
b *= scale;
c0 = r;
c1 = g;
c2 = b;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}

82
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs

@ -1,82 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYccKAvx : JpegColorConverterAvx
{
public FromYccKAvx(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = c0;
Vector256<float> cb = Avx.Add(c1, chromaOffset);
Vector256<float> cr = Avx.Add(c2, chromaOffset);
Vector256<float> scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g =
HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
r = Avx.Subtract(max, Avx.RoundToNearestInteger(r));
g = Avx.Subtract(max, Avx.RoundToNearestInteger(g));
b = Avx.Subtract(max, Avx.RoundToNearestInteger(b));
r = Avx.Multiply(r, scaledK);
g = Avx.Multiply(g, scaledK);
b = Avx.Multiply(b, scaledK);
c0 = r;
c1 = g;
c2 = b;
}
}
}
}
}
#endif

43
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs

@ -1,43 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYccKScalar : JpegColorConverterScalar
{
public FromYccKScalar(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < values.Component0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
float scaledK = c3[i] * scale;
c0[i] = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * scaledK;
c1[i] = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * scaledK;
c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK;
}
}
}
}
}

75
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs

@ -1,75 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYccKVector : JpegColorConverterVector
{
public FromYccKVector(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
var max = new Vector<float>(this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
// cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F;
// k = kVals[i] / 256F;
ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> y = c0;
Vector<float> cb = c1 + chromaOffset;
Vector<float> cr = c2 + chromaOffset;
Vector<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector<float> r = y + (cr * rCrMult);
Vector<float> g = y + (cb * gCbMult) + (cr * gCrMult);
Vector<float> b = y + (cb * bCbMult);
r = (max - r.FastRound()) * scaledK;
g = (max - g.FastRound()) * scaledK;
b = (max - b.FastRound()) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}

61
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector"/> API.
/// </summary>
/// <remarks>
/// Converters of this family can work with data of any size.
/// Even though real life data is guaranteed to be of size
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
/// such data out of the box. These converters have fallback code
/// for remainder data.
/// </remarks>
internal abstract class JpegColorConverterVector : JpegColorConverterBase
{
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0;
public sealed override int ElementsPerBatch => Vector<float>.Count;
public sealed override void ConvertToRgbInplace(in ComponentValues values)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
int length = values.Component0.Length;
int remainder = (int)((uint)length % (uint)Vector<float>.Count);
// Jpeg images are guaranteed to have pixel strides at least 8 pixels wide
// Thus there's no need to check whether simdCount is greater than zero
int simdCount = length - remainder;
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount));
// Jpeg images width is always divisible by 8 without a remainder
// so it's safe to say SSE/AVX implementations would never have
// 'remainder' pixels
// But some exotic simd implementations e.g. AVX-512 can have
// remainder pixels
if (remainder > 0)
{
this.ConvertCoreInplace(values.Slice(simdCount, remainder));
}
}
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException();
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException();
}
}
}

8
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10;
this.Precision = precision;
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets a value indicating whether the frame uses the extended specification.
/// </summary>
public bool Extended { get; private set; }
public bool IsExtended { get; private set; }
/// <summary>
/// Gets a value indicating whether the frame uses the progressive specification.
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <remarks>
/// This is true for progressive and baseline non-interleaved images.
/// </remarks>
public bool MultiScan { get; set; }
public bool Interleaved { get; set; }
/// <summary>
/// Gets the precision.
@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public void AllocateComponents()
{
bool fullScan = this.Progressive || this.MultiScan;
bool fullScan = this.Progressive || !this.Interleaved;
for (int i = 0; i < this.ComponentCount; i++)
{
IJpegComponent component = this.Components[i];

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -1,12 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Converter used to convert jpeg spectral data to color pixels.
/// Converter used to convert jpeg spectral data to pixels.
/// </summary>
internal abstract class SpectralConverter
{

1
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -5,7 +5,6 @@ using System;
using System.Buffers;
using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

116
src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs

@ -0,0 +1,116 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Represents a single frame component.
/// </summary>
internal class Component : IDisposable
{
private readonly MemoryAllocator memoryAllocator;
public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex)
{
this.memoryAllocator = memoryAllocator;
this.HorizontalSamplingFactor = horizontalFactor;
this.VerticalSamplingFactor = verticalFactor;
this.SamplingFactors = new Size(horizontalFactor, verticalFactor);
this.QuantizationTableIndex = quantizationTableIndex;
}
/// <summary>
/// Gets or sets DC coefficient predictor.
/// </summary>
public int DcPredictor { get; set; }
/// <summary>
/// Gets the horizontal sampling factor.
/// </summary>
public int HorizontalSamplingFactor { get; }
/// <summary>
/// Gets the vertical sampling factor.
/// </summary>
public int VerticalSamplingFactor { get; }
public Buffer2D<Block8x8> SpectralBlocks { get; private set; }
public Size SubSamplingDivisors { get; private set; }
public int QuantizationTableIndex { get; }
public Size SizeInBlocks { get; private set; }
public Size SamplingFactors { get; set; }
/// <summary>
/// Gets the number of blocks per line.
/// </summary>
public int WidthInBlocks { get; private set; }
/// <summary>
/// Gets the number of blocks per column.
/// </summary>
public int HeightInBlocks { get; private set; }
/// <summary>
/// Gets or sets the index for the DC Huffman table.
/// </summary>
public int DcTableId { get; set; }
/// <summary>
/// Gets or sets the index for the AC Huffman table.
/// </summary>
public int AcTableId { get; set; }
/// <inheritdoc/>
public void Dispose()
{
this.SpectralBlocks?.Dispose();
this.SpectralBlocks = null;
}
/// <summary>
/// Initializes component for future buffers initialization.
/// </summary>
/// <param name="frame">asdfasdf.</param>
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV)
{
uint widthInBlocks = ((uint)frame.PixelWidth + 7) / 8;
uint heightInBlocks = ((uint)frame.PixelHeight + 7) / 8;
this.WidthInBlocks = (int)MathF.Ceiling(
(float)widthInBlocks * this.HorizontalSamplingFactor / maxSubFactorH);
this.HeightInBlocks = (int)MathF.Ceiling(
(float)heightInBlocks * this.VerticalSamplingFactor / maxSubFactorV);
int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor;
int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor;
this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu);
this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors);
if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0)
{
JpegThrowHelper.ThrowBadSampling();
}
}
public void AllocateSpectral(bool fullScan)
{
int spectralAllocWidth = this.SizeInBlocks.Width;
int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor;
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(spectralAllocWidth, spectralAllocHeight);
}
}
}

231
src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs

@ -0,0 +1,231 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class ComponentProcessor : IDisposable
{
private readonly Size blockAreaSize;
private readonly Component component;
private Block8x8F quantTable;
public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable)
{
this.component = component;
this.quantTable = quantTable;
this.component = component;
this.blockAreaSize = component.SubSamplingDivisors * 8;
// alignment of 8 so each block stride can be sampled from a single 'ref pointer'
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
8,
AllocationOptions.Clean);
}
/// <summary>
/// Gets the temporary working buffer of color values.
/// </summary>
public Buffer2D<float> ColorBuffer { get; }
public void CopyColorBufferToBlocks(int spectralStep)
{
Buffer2D<Block8x8> spectralBuffer = this.component.SpectralBlocks;
int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = spectralStep * this.component.SamplingFactors.Height;
Block8x8F workspaceBlock = default;
// handle subsampling
Size subsamplingFactors = this.component.SubSamplingDivisors;
if (subsamplingFactors.Width != 1 || subsamplingFactors.Height != 1)
{
this.PackColorBuffer();
}
int blocksRowsPerStep = this.component.SamplingFactors.Height;
for (int y = 0; y < blocksRowsPerStep; y++)
{
int yBuffer = y * this.blockAreaSize.Height;
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
// load 8x8 block from 8 pixel strides
int xColorBufferStart = xBlock * 8;
workspaceBlock.ScaledCopyFrom(
ref colorBufferRow[xColorBufferStart],
destAreaStride);
// level shift via -128f
workspaceBlock.AddInPlace(-128f);
// FDCT
FloatingPointDCT.TransformFDCT(ref workspaceBlock);
// Quantize and save to spectral blocks
Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable);
}
}
}
public Span<float> GetColorBufferRowSpan(int row)
=> this.ColorBuffer.DangerousGetRowSpan(row);
public void Dispose()
=> this.ColorBuffer.Dispose();
private void PackColorBuffer()
{
Size factors = this.component.SubSamplingDivisors;
int packedWidth = this.ColorBuffer.Width / factors.Width;
float averageMultiplier = 1f / (factors.Width * factors.Height);
for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height)
{
Span<float> sourceRow = this.ColorBuffer.DangerousGetRowSpan(i);
// vertical sum
for (int j = 1; j < factors.Height; j++)
{
SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j));
}
// horizontal sum
SumHorizontal(sourceRow, factors.Width);
// calculate average
MultiplyToAverage(sourceRow, averageMultiplier);
// copy to the first 8 slots
sourceRow.Slice(0, packedWidth).CopyTo(this.ColorBuffer.DangerousGetRowSpan(i / factors.Height));
}
static void SumVertical(Span<float> target, Span<float> source)
{
if (Avx.IsSupported)
{
ref Vector256<float> targetVectorRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(target));
ref Vector256<float> sourceVectorRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(source));
// Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed
nint count = source.Length / Vector256<float>.Count;
for (nint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i));
}
}
else
{
ref Vector<float> targetVectorRef = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(target));
ref Vector<float> sourceVectorRef = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
nint count = source.Length / Vector<float>.Count;
for (nint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i);
}
ref float targetRef = ref MemoryMarshal.GetReference(target);
ref float sourceRef = ref MemoryMarshal.GetReference(source);
for (nint i = count * Vector<float>.Count; i < source.Length; i++)
{
Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i);
}
}
}
static void SumHorizontal(Span<float> target, int factor)
{
Span<float> source = target;
if (Avx2.IsSupported)
{
ref Vector256<float> targetRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(target));
// Ideally we need to use log2: Numerics.Log2((uint)factor)
// but division by 2 works just fine in this case
int haddIterationsCount = (int)((uint)factor / 2);
// Transform spans so that it only contains 'remainder'
// values for the scalar fallback code
int scalarRemainder = target.Length % (Vector<float>.Count * factor);
int touchedCount = target.Length - scalarRemainder;
source = source.Slice(touchedCount);
target = target.Slice(touchedCount / factor);
uint length = (uint)touchedCount / (uint)Vector256<float>.Count;
for (int i = 0; i < haddIterationsCount; i++)
{
length /= 2;
for (nuint j = 0; j < length; j++)
{
nuint indexLeft = j * 2;
nuint indexRight = indexLeft + 1;
Vector256<float> sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight));
Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle();
}
}
}
// scalar remainder
for (int i = 0; i < source.Length / factor; i++)
{
target[i] = source[i * factor];
for (int j = 1; j < factor; j++)
{
target[i] += source[(i * factor) + j];
}
}
}
static void MultiplyToAverage(Span<float> target, float multiplier)
{
if (Avx.IsSupported)
{
ref Vector256<float> targetVectorRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(target));
// Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed
nint count = target.Length / Vector256<float>.Count;
var multiplierVector = Vector256.Create(multiplier);
for (nint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector);
}
}
else
{
ref Vector<float> targetVectorRef = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(target));
nint count = target.Length / Vector<float>.Count;
var multiplierVector = new Vector<float>(multiplier);
for (nint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVectorRef, i) *= multiplierVector;
}
ref float targetRef = ref MemoryMarshal.GetReference(target);
for (nint i = count * Vector<float>.Count; i < target.Length; i++)
{
Unsafe.Add(ref targetRef, i) *= multiplier;
}
}
}
}
}
}

30
src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class JpegComponentConfig
{
public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex)
{
this.Id = id;
this.HorizontalSampleFactor = hsf;
this.VerticalSampleFactor = vsf;
this.QuantizatioTableIndex = quantIndex;
this.DcTableSelector = dcIndex;
this.AcTableSelector = acIndex;
}
public byte Id { get; }
public int HorizontalSampleFactor { get; }
public int VerticalSampleFactor { get; }
public int QuantizatioTableIndex { get; }
public int DcTableSelector { get; }
public int AcTableSelector { get; }
}
}

44
src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class JpegFrameConfig
{
public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables)
{
this.ColorType = colorType;
this.EncodingColor = encodingColor;
this.Components = components;
this.HuffmanTables = huffmanTables;
this.QuantizationTables = quantTables;
this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor;
this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor;
for (int i = 1; i < components.Length; i++)
{
JpegComponentConfig component = components[i];
this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor);
this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor);
}
}
public JpegColorSpace ColorType { get; }
public JpegEncodingColor EncodingColor { get; }
public JpegComponentConfig[] Components { get; }
public JpegHuffmanTableConfig[] HuffmanTables { get; }
public JpegQuantizationTableConfig[] QuantizationTables { get; }
public int MaxHorizontalSamplingFactor { get; }
public int MaxVerticalSamplingFactor { get; }
public byte? AdobeColorTransformMarkerFlag { get; set; } = null;
}
}

21
src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class JpegHuffmanTableConfig
{
public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table)
{
this.Class = @class;
this.DestinationIndex = destIndex;
this.Table = table;
}
public int Class { get; }
public int DestinationIndex { get; }
public HuffmanSpec Table { get; }
}
}

20
src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class JpegQuantizationTableConfig
{
public JpegQuantizationTableConfig(int destIndex, ReadOnlySpan<byte> quantizationTable)
{
this.DestinationIndex = destIndex;
this.Table = Block8x8.Load(quantizationTable);
}
public int DestinationIndex { get; }
public Block8x8 Table { get; }
}
}

35
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs

@ -1,35 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the Huffman tables
/// </summary>
internal enum HuffIndex
{
/// <summary>
/// The DC luminance huffman table index
/// </summary>
LuminanceDC = 0,
// ReSharper disable UnusedMember.Local
/// <summary>
/// The AC luminance huffman table index
/// </summary>
LuminanceAC = 1,
/// <summary>
/// The DC chrominance huffman table index
/// </summary>
ChrominanceDC = 2,
/// <summary>
/// The AC chrominance huffman table index
/// </summary>
ChrominanceAC = 3,
// ReSharper restore UnusedMember.Local
}
}

17
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs

@ -26,23 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </remarks>
internal readonly struct HuffmanLut
{
/// <summary>
/// The compiled representations of theHuffmanSpec.
/// </summary>
public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4];
/// <summary>
/// Initializes static members of the <see cref="HuffmanLut"/> struct.
/// </summary>
static HuffmanLut()
{
// Initialize the Huffman tables
for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++)
{
TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanLut"/> struct.
/// </summary>

376
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -7,7 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@ -60,10 +59,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private const int OutputBufferLengthMultiplier = 2;
/// <summary>
/// Compiled huffman tree to encode given values.
/// The DC Huffman tables.
/// </summary>
/// <remarks>Yields codewords by index consisting of [run length | bitsize].</remarks>
private HuffmanLut[] huffmanTables;
private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4];
/// <summary>
/// The AC Huffman tables.
/// </summary>
private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4];
/// <summary>
/// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>.
@ -94,8 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private int emitWriteIndex;
private Block8x8 tempBlock;
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
/// </summary>
@ -128,57 +129,67 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2;
}
public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig)
{
HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table);
}
/// <summary>
/// Encodes the image with no subsampling.
/// Encodes scan in baseline interleaved mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
/// <param name="color">Output color space.</param>
/// <param name="frame">Frame to encode.</param>
/// <param name="converter">Converter from color to spectral.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeScanBaselineInterleaved<TPixel>(JpegEncodingColor color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
FloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
switch (color)
{
case JpegEncodingColor.YCbCrRatio444:
case JpegEncodingColor.Rgb:
this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken);
break;
default:
this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken);
break;
}
}
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
/// <summary>
/// Encodes grayscale scan in baseline interleaved mode.
/// </summary>
/// <param name="component">Component with grayscale data.</param>
/// <param name="converter">Converter from color to spectral.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeScanBaselineSingleComponent<TPixel>(Component component, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
int h = component.HeightInBlocks;
int w = component.WidthInBlocks;
var pixelConverter = new YCbCrForwardConverter444<TPixel>(frame);
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int y = 0; y < pixels.Height; y += 8)
for (int i = 0; i < h; i++)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
// Convert from pixels to spectral via given converter
converter.ConvertStrideBaseline();
// Encode spectral to binary
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (nint k = 0; k < w; k++)
{
pixelConverter.Convert(x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref luminanceQuantTable);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref chrominanceQuantTable);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref chrominanceQuantTable);
this.WriteBlock(
component,
ref Unsafe.Add(ref blockRef, k),
ref dcHuffmanTable,
ref acHuffmanTable);
if (this.IsStreamFlushNeeded)
{
@ -191,65 +202,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
/// <summary>
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
/// at a factor of 2 both horizontally and vertically.
/// Encodes scan with a single component in baseline non-interleaved mode.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
/// <param name="component">Component with grayscale data.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeScanBaseline(Component component, CancellationToken cancellationToken)
{
FloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
FloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
int h = component.HeightInBlocks;
int w = component.WidthInBlocks;
var pixelConverter = new YCbCrForwardConverter420<TPixel>(frame);
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int y = 0; y < pixels.Height; y += 16)
for (int i = 0; i < h; i++)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 2; i++)
{
int yOff = i * 8;
currentRows.Update(pixelBuffer, y + yOff);
pixelConverter.Convert(x, y, ref currentRows, i);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.YLeft,
ref luminanceQuantTable);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.YRight,
ref luminanceQuantTable);
}
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref chrominanceQuantTable);
// Encode spectral to binary
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref chrominanceQuantTable);
for (nint k = 0; k < w; k++)
{
this.WriteBlock(
component,
ref Unsafe.Add(ref blockRef, k),
ref dcHuffmanTable,
ref acHuffmanTable);
if (this.IsStreamFlushNeeded)
{
@ -262,43 +241,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
/// <summary>
/// Encodes the image with no chroma, just luminance.
/// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
/// <param name="frame">Frame to encode.</param>
/// <param name="converter">Converter from color to spectral.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
private void EncodeScanBaselineInterleaved<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
nint mcu = 0;
nint mcusPerColumn = frame.McusPerColumn;
nint mcusPerLine = frame.McusPerLine;
this.huffmanTables = HuffmanLut.TheHuffmanLut;
for (int j = 0; j < mcusPerColumn; j++)
{
cancellationToken.ThrowIfCancellationRequested();
// ReSharper disable once InconsistentNaming
int prevDCY = 0;
// Convert from pixels to spectral via given converter
converter.ConvertStrideBaseline();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
// Encode spectral to binary
for (nint i = 0; i < mcusPerLine; i++)
{
// Scan an interleaved mcu... process components in order
nint mcuCol = mcu % mcusPerLine;
for (nint k = 0; k < frame.Components.Length; k++)
{
Component component = frame.Components[k];
var pixelConverter = new LuminanceForwardConverter<TPixel>(frame);
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
nint h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(x, y, ref currentRows);
nint blockColBase = mcuCol * h;
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref luminanceQuantTable);
// Scan out an mcu's worth of this component; that's just determined
// by the basic H and V specified for the component
for (int y = 0; y < v; y++)
{
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (nint x = 0; x < h; x++)
{
nint blockCol = blockColBase + x;
this.WriteBlock(
component,
ref Unsafe.Add(ref blockRef, blockCol),
ref dcHuffmanTable,
ref acHuffmanTable);
}
}
}
// After all interleaved components, that's an interleaved MCU
mcu++;
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
@ -310,54 +310,59 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
/// <summary>
/// Encodes the image with no subsampling and keeps the pixel data as Rgb24.
/// Encodes scan in baseline interleaved mode with exactly 3 components with no subsampling.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="quantTable">Quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeRgb<TPixel>(Image<TPixel> pixels, ref Block8x8F quantTable, CancellationToken cancellationToken)
/// <param name="frame">Frame to encode.</param>
/// <param name="converter">Converter from color to spectral.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
private void EncodeThreeComponentBaselineInterleavedScanNoSubsampling<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FloatingPointDCT.AdjustToFDCT(ref quantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
nint mcusPerColumn = frame.McusPerColumn;
nint mcusPerLine = frame.McusPerLine;
// ReSharper disable once InconsistentNaming
int prevDCR = 0, prevDCG = 0, prevDCB = 0;
Component c2 = frame.Components[2];
Component c1 = frame.Components[1];
Component c0 = frame.Components[0];
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId];
ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId];
ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId];
ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId];
ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId];
ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId];
var pixelConverter = new RgbForwardConverter<TPixel>(frame);
ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0));
ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0));
ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0));
for (int y = 0; y < pixels.Height; y += 8)
for (nint j = 0; j < mcusPerColumn; j++)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
// Convert from pixels to spectral via given converter
converter.ConvertStrideBaseline();
// Encode spectral to binary
for (nint i = 0; i < mcusPerLine; i++)
{
pixelConverter.Convert(x, y, ref currentRows);
prevDCR = this.WriteBlock(
QuantIndex.Luminance,
prevDCR,
ref pixelConverter.R,
ref quantTable);
prevDCG = this.WriteBlock(
QuantIndex.Luminance,
prevDCG,
ref pixelConverter.G,
ref quantTable);
prevDCB = this.WriteBlock(
QuantIndex.Luminance,
prevDCB,
ref pixelConverter.B,
ref quantTable);
this.WriteBlock(
c0,
ref Unsafe.Add(ref c0BlockRef, i),
ref c0dcHuffmanTable,
ref c0acHuffmanTable);
this.WriteBlock(
c1,
ref Unsafe.Add(ref c1BlockRef, i),
ref c1dcHuffmanTable,
ref c1acHuffmanTable);
this.WriteBlock(
c2,
ref Unsafe.Add(ref c2BlockRef, i),
ref c2dcHuffmanTable,
ref c2acHuffmanTable);
if (this.IsStreamFlushNeeded)
{
@ -369,44 +374,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.FlushRemainingBytes();
}
/// <summary>
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
/// The block is in natural (not zig-zag) order.
/// </summary>
/// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param>
/// <param name="block">Source block.</param>
/// <param name="quant">Quantization table.</param>
/// <returns>The <see cref="int"/>.</returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
ref Block8x8F block,
ref Block8x8F quant)
private void WriteBlock(
Component component,
ref Block8x8 block,
ref HuffmanLut dcTable,
ref HuffmanLut acTable)
{
ref Block8x8 spectralBlock = ref this.tempBlock;
// Shifting level from 0..255 to -128..127
block.AddInPlace(-128f);
// Discrete cosine transform
FloatingPointDCT.TransformFDCT(ref block);
// Quantization
Block8x8F.Quantize(ref block, ref spectralBlock, ref quant);
// Emit the DC delta.
int dc = spectralBlock[0];
this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC);
int dc = block[0];
this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor);
component.DcPredictor = dc;
// Emit the AC components.
int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values;
int[] acHuffTable = acTable.Values;
nint lastValuableIndex = spectralBlock.GetLastNonZeroIndex();
nint lastValuableIndex = block.GetLastNonZeroIndex();
int runLength = 0;
ref short blockRef = ref Unsafe.As<Block8x8, short>(ref spectralBlock);
ref short blockRef = ref Unsafe.As<Block8x8, short>(ref block);
for (nint zig = 1; zig <= lastValuableIndex; zig++)
{
const int zeroRun1 = 1 << 4;
@ -437,8 +422,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
this.EmitHuff(acHuffTable, 0x00);
}
return dc;
}
/// <summary>
@ -655,7 +638,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Flushes spectral data bytes after encoding all channel blocks
/// in a single jpeg macroblock using <see cref="WriteBlock"/>.
/// in a single jpeg macroblock using <see cref="WriteBlock(Component, ref Block8x8, ref HuffmanLut, ref HuffmanLut)"/>.
/// </summary>
/// <remarks>
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
@ -684,6 +667,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// Flush cached bytes to the output stream with padding bits
int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount;
this.FlushToStream(lastByteIndex);
// Clear huffman register
// This is needed for for images with multiples scans
this.bitCount = 0;
this.accumulatedBits = 0;
}
}
}

205
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs

@ -6,111 +6,122 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// The Huffman encoding specifications.
/// </summary>
internal readonly struct HuffmanSpec
public readonly struct HuffmanSpec
{
#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines
/// <summary>
/// Huffman talbe specification for luminance DC.
/// </summary>
/// <remarks>
/// This is an example specification taken from the jpeg specification paper.
/// </remarks>
public static readonly HuffmanSpec LuminanceDC = new(
new byte[]
{
0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
});
/// <summary>
/// The Huffman encoding specifications.
/// This encoder uses the same Huffman encoding for all images.
/// Huffman talbe specification for luminance AC.
/// </summary>
public static readonly HuffmanSpec[] TheHuffmanSpecs =
/// <remarks>
/// This is an example specification taken from the jpeg specification paper.
/// </remarks>
public static readonly HuffmanSpec LuminanceAC = new(
new byte[]
{
// Luminance DC.
new HuffmanSpec(
new byte[]
{
0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0,
0, 1, 125
},
new byte[]
{
0x01, 0x02, 0x03, 0x00, 0x04, 0x11,
0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13,
0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56,
0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4,
0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
});
// Luminance AC.
new HuffmanSpec(
new byte[]
{
0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0,
0, 1, 125
},
new byte[]
{
0x01, 0x02, 0x03, 0x00, 0x04, 0x11,
0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13,
0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56,
0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4,
0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
}),
/// <summary>
/// Huffman talbe specification for chrominance DC.
/// </summary>
/// <remarks>
/// This is an example specification taken from the jpeg specification paper.
/// </remarks>
public static readonly HuffmanSpec ChrominanceDC = new(
new byte[]
{
0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
});
// Chrominance DC.
new HuffmanSpec(
new byte[]
{
0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
/// <summary>
/// Huffman talbe specification for chrominance DC.
/// </summary>
/// <remarks>
/// This is an example specification taken from the jpeg specification paper.
/// </remarks>
public static readonly HuffmanSpec ChrominanceAC = new(
new byte[]
{
0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0,
1, 2, 119
},
new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81,
0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62,
0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73,
0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2,
0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
});
// Chrominance AC.
new HuffmanSpec(
new byte[]
{
0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0,
1, 2, 119
},
new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81,
0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62,
0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73,
0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2,
0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
})
};
#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines
/// <summary>
/// Gets count[i] - The number of codes of length i bits.
/// </summary>

85
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs

@ -0,0 +1,85 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Represent a single jpeg frame.
/// </summary>
internal sealed class JpegFrame : IDisposable
{
public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved)
{
this.ColorSpace = frameConfig.ColorType;
this.Interleaved = interleaved;
this.PixelWidth = image.Width;
this.PixelHeight = image.Height;
MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator;
JpegComponentConfig[] componentConfigs = frameConfig.Components;
this.Components = new Component[componentConfigs.Length];
for (int i = 0; i < this.Components.Length; i++)
{
JpegComponentConfig componentConfig = componentConfigs[i];
this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex)
{
DcTableId = componentConfig.DcTableSelector,
AcTableId = componentConfig.AcTableSelector,
};
this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor;
}
int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor;
int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor;
this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.Components.Length; i++)
{
Component component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV);
}
}
public JpegColorSpace ColorSpace { get; }
public bool Interleaved { get; }
public int PixelHeight { get; }
public int PixelWidth { get; }
public Component[] Components { get; }
public int McusPerLine { get; }
public int McusPerColumn { get; }
public int BlocksPerMcu { get; }
public void Dispose()
{
for (int i = 0; i < this.Components.Length; i++)
{
this.Components[i].Dispose();
}
}
public void AllocateComponents(bool fullScan)
{
for (int i = 0; i < this.Components.Length; i++)
{
Component component = this.Components[i];
component.AllocateSpectral(fullScan);
}
}
}
}

127
src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs

@ -1,127 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> L8 -> Y conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct LuminanceForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// Temporal 64-pixel span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted <see cref="L8"/> data.
/// </summary>
private readonly Span<L8> l8Span;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private readonly Configuration config;
public LuminanceForwardConverter(ImageFrame<TPixel> frame)
{
this.Y = default;
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.l8Span = new L8[PixelsPerSample].AsSpan();
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>)
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToL8(this.config, this.pixelSpan, this.l8Span);
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span);
if (RgbToYCbCrConverterVectorized.IsSupported)
{
ConvertAvx(ref l8Start, ref yBlock);
}
else
{
ConvertScalar(ref l8Start, ref yBlock);
}
}
/// <summary>
/// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
/// </summary>
/// <param name="l8Start">Start of span of L8 pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock)
{
Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector128<byte> l8ByteSpan = ref Unsafe.As<L8, Vector128<byte>>(ref l8Start);
ref Vector256<float> destRef = ref yBlock.V0;
const int bytesPerL8Stride = 8;
for (nint i = 0; i < 8; i++)
{
Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i)));
}
#endif
}
/// <summary>
/// Converts 8x8 L8 pixel matrix to 8x8 Block of floats.
/// </summary>
/// <param name="l8Start">Start of span of L8 pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock)
{
for (int i = 0; i < Block8x8F.Size; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);
yBlock[i] = c.PackedValue;
}
}
}
}

21
src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs

@ -1,21 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the quantization tables.
/// </summary>
internal enum QuantIndex
{
/// <summary>
/// The luminance quantization table index.
/// </summary>
Luminance = 0,
/// <summary>
/// The chrominance quantization table index.
/// </summary>
Chrominance = 1,
}
}

165
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs

@ -1,165 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on.</typeparam>
internal ref struct RgbForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// The Red component.
/// </summary>
public Block8x8F R;
/// <summary>
/// The Green component.
/// </summary>
public Block8x8F G;
/// <summary>
/// The Blue component.
/// </summary>
public Block8x8F B;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data.
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private readonly Configuration config;
public RgbForwardConverter(ImageFrame<TPixel> frame)
{
this.R = default;
this.G = default;
this.B = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
ref Block8x8F redBlock = ref this.R;
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;
if (RgbToYCbCrConverterVectorized.IsSupported)
{
ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
else
{
ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
}
/// <summary>
/// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
/// </summary>
/// <param name="rgbSpan">Span of Rgb24 pixels with size of 64</param>
/// <param name="rBlock">8x8 destination matrix of Red converted data</param>
/// <param name="gBlock">8x8 destination matrix of Blue converted data</param>
/// <param name="bBlock">8x8 destination matrix of Green converted data</param>
private static void ConvertAvx(Span<Rgb24> rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock)
{
Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> redRef = ref rBlock.V0;
ref Vector256<float> greenRef = ref gBlock.V0;
ref Vector256<float> blueRef = ref bBlock.V0;
var zero = Vector256.Create(0).AsByte();
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb));
Vector256<byte> rgb, rg, bx;
const int bytesPerRgbStride = 24;
for (nint i = 0; i < 8; i++)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
}
#endif
}
private static void ConvertScalar(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);
for (int i = 0; i < Block8x8F.Size; i++)
{
Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i);
redBlock[i] = c.R;
greenBlock[i] = c.G;
blueBlock[i] = c.B;
}
}
}
}

237
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs

@ -1,237 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
/// Methods to build the tables are based on libjpeg implementation.
/// </summary>
internal unsafe struct RgbToYCbCrConverterLut
{
/// <summary>
/// The red luminance table
/// </summary>
public fixed int YRTable[256];
/// <summary>
/// The green luminance table
/// </summary>
public fixed int YGTable[256];
/// <summary>
/// The blue luminance table
/// </summary>
public fixed int YBTable[256];
/// <summary>
/// The red blue-chrominance table
/// </summary>
public fixed int CbRTable[256];
/// <summary>
/// The green blue-chrominance table
/// </summary>
public fixed int CbGTable[256];
/// <summary>
/// The blue blue-chrominance table
/// B=>Cb and R=>Cr are the same
/// </summary>
public fixed int CbBTable[256];
/// <summary>
/// The green red-chrominance table
/// </summary>
public fixed int CrGTable[256];
/// <summary>
/// The blue red-chrominance table
/// </summary>
public fixed int CrBTable[256];
// Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
private const int ScaleBits = 16;
private const int CBCrOffset = 128 << ScaleBits;
private const int Half = 1 << (ScaleBits - 1);
/// <summary>
/// Initializes the YCbCr tables
/// </summary>
/// <returns>The initialized <see cref="RgbToYCbCrConverterLut"/></returns>
public static RgbToYCbCrConverterLut Create()
{
RgbToYCbCrConverterLut tables = default;
for (int i = 0; i <= 255; i++)
{
// The values for the calculations are left scaled up since we must add them together before rounding.
tables.YRTable[i] = Fix(0.299F) * i;
tables.YGTable[i] = Fix(0.587F) * i;
tables.YBTable[i] = (Fix(0.114F) * i) + Half;
tables.CbRTable[i] = (-Fix(0.168735892F)) * i;
tables.CbGTable[i] = (-Fix(0.331264108F)) * i;
// We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr.
// This ensures that the maximum output will round to 255
// not 256, and thus that we don't have to range-limit.
//
// B=>Cb and R=>Cr tables are the same
tables.CbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1;
tables.CrGTable[i] = (-Fix(0.418687589F)) * i;
tables.CrBTable[i] = (-Fix(0.081312411F)) * i;
}
return tables;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateY(byte r, byte g, byte b)
{
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateCb(byte r, byte g, byte b)
{
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateCr(byte r, byte g, byte b)
{
// float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
}
/// <summary>
/// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma.
/// </summary>
/// <param name="rgbSpan">Span of Rgb24 pixel data</param>
/// <param name="yBlock">Resulting Y values block</param>
/// <param name="cbBlock">Resulting Cb values block</param>
/// <param name="crBlock">Resulting Cr values block</param>
public void Convert444(Span<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < Block8x8F.Size; i++)
{
Rgb24 c = Unsafe.Add(ref rgbStart, i);
yBlock[i] = this.CalculateY(c.R, c.G, c.B);
cbBlock[i] = this.CalculateCb(c.R, c.G, c.B);
crBlock[i] = this.CalculateCr(c.R, c.G, c.B);
}
}
/// <summary>
/// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma.
/// </summary>
/// <remarks>Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param.</remarks>
/// <param name="rgbSpan">Span of Rgb24 pixel data</param>
/// <param name="yBlockLeft">First or "left" resulting Y block</param>
/// <param name="yBlockRight">Second or "right" resulting Y block</param>
/// <param name="cbBlock">Resulting Cb values block</param>
/// <param name="crBlock">Resulting Cr values block</param>
/// <param name="row">Row index of the 16x16 block, 0 or 1</param>
public void Convert420(Span<Rgb24> rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
{
DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row));
ref float yBlockLeftRef = ref Unsafe.As<Block8x8F, float>(ref yBlockLeft);
ref float yBlockRightRef = ref Unsafe.As<Block8x8F, float>(ref yBlockRight);
// 0-31 or 32-63
// upper or lower part
int chromaWriteOffset = row * (Block8x8F.Size / 2);
ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, float>(ref cbBlock), chromaWriteOffset);
ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, float>(ref crBlock), chromaWriteOffset);
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 8; i += 2)
{
int yBlockWriteOffset = i * 8;
ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16);
int chromaOffset = 8 * (i / 2);
// left
this.ConvertChunk420(
ref stride,
ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset),
ref Unsafe.Add(ref cbBlockRef, chromaOffset),
ref Unsafe.Add(ref crBlockRef, chromaOffset));
// right
this.ConvertChunk420(
ref Unsafe.Add(ref stride, 8),
ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset),
ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4),
ref Unsafe.Add(ref crBlockRef, chromaOffset + 4));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock)
{
// jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons)
// each row is 16 pixels wide thus +16 stride reference offset
// resulting luminance (Y`) are sampled at original resolution thus +8 reference offset
for (int k = 0; k < 8; k += 2)
{
ref float yBlockRef = ref Unsafe.Add(ref yBlock, k);
// top row
Rgb24 px0 = Unsafe.Add(ref stride, k);
Rgb24 px1 = Unsafe.Add(ref stride, k + 1);
yBlockRef = this.CalculateY(px0.R, px0.G, px0.B);
Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B);
// bottom row
Rgb24 px2 = Unsafe.Add(ref stride, k + 16);
Rgb24 px3 = Unsafe.Add(ref stride, k + 17);
Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B);
Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B);
// chroma average for 2x2 pixel block
Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3);
Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
{
return 0.25f
* (this.CalculateCb(px0.R, px0.G, px0.B)
+ this.CalculateCb(px1.R, px1.G, px1.B)
+ this.CalculateCb(px2.R, px2.G, px2.B)
+ this.CalculateCb(px3.R, px3.G, px3.B));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
{
return 0.25f
* (this.CalculateCr(px0.R, px0.G, px0.B)
+ this.CalculateCr(px1.R, px1.G, px1.B)
+ this.CalculateCr(px2.R, px2.G, px2.B)
+ this.CalculateCr(px3.R, px3.G, px3.B));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x)
=> (int)((x * (1L << ScaleBits)) + 0.5F);
}
}

259
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs

@ -1,259 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Diagnostics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal static class RgbToYCbCrConverterVectorized
{
public static bool IsSupported
{
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
return Avx2.IsSupported;
#else
return false;
#endif
}
}
public static int AvxCompatibilityPadding
{
// rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total
// Strides are stored sequentially - one big span of 64 * 3 = 192 bytes
// Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits
// Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride:
// stride 0 0 - 192 -(+64bits)-> 256
// stride 1 192 - 384 -(+64bits)-> 448
// stride 2 384 - 576 -(+64bits)-> 640
// stride 3 576 - 768 -(+64bits)-> 832
// stride 4 768 - 960 -(+64bits)-> 1024
// stride 5 960 - 1152 -(+64bits)-> 1216
// stride 6 1152 - 1344 -(+64bits)-> 1408
// stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION
//
// Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits
// This is not permitted - we are reading foreign memory
//
// 8 byte padding to rgb byte span will solve this problem without extra code in converters
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (IsSupported)
{
return 8;
}
#endif
return 0;
}
}
#if SUPPORTS_RUNTIME_INTRINSICS
internal static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
{
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
};
internal static ReadOnlySpan<byte> ExtractRgb => new byte[]
{
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF
};
#endif
/// <summary>
/// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling
/// </summary>
/// <remarks>Total size of rgb span must be 200 bytes</remarks>
/// <param name="rgbSpan">Span of rgb pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
/// <param name="cbBlock">8x8 destination matrix of Chrominance(Cb) converted data</param>
/// <param name="crBlock">8x8 destination matrix of Chrominance(Cr) converted data</param>
public static void Convert444(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{
Debug.Assert(IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var f128 = Vector256.Create(128f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> destYRef = ref yBlock.V0;
ref Vector256<float> destCbRef = ref cbBlock.V0;
ref Vector256<float> destCrRef = ref crBlock.V0;
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
const int bytesPerRgbStride = 24;
for (int i = 0; i < 8; i++)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
}
#endif
}
/// <summary>
/// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling
/// </summary>
public static void Convert420(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
{
Debug.Assert(IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var f128 = Vector256.Create(128f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
int destOffset = row * 4;
ref Vector256<float> destCbRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock), destOffset);
ref Vector256<float> destCrRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock), destOffset);
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
Span<Vector256<float>> rDataLanes = stackalloc Vector256<float>[4];
Span<Vector256<float>> gDataLanes = stackalloc Vector256<float>[4];
Span<Vector256<float>> bDataLanes = stackalloc Vector256<float>[4];
const int bytesPerRgbStride = 24;
for (int i = 0; i < 4; i++)
{
// 16x2 => 8x1
// left 8x8 column conversions
for (int j = 0; j < 4; j += 2)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
rDataLanes[j] = r;
gDataLanes[j] = g;
bDataLanes[j] = b;
}
// 16x2 => 8x1
// right 8x8 column conversions
for (int j = 1; j < 4; j += 2)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
rDataLanes[j] = r;
gDataLanes[j] = g;
bDataLanes[j] = b;
}
r = Scale16x2_8x1(rDataLanes);
g = Scale16x2_8x1(gDataLanes);
b = Scale16x2_8x1(bDataLanes);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
}
#endif
}
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Scales 16x2 matrix to 8x1 using 2x2 average
/// </summary>
/// <param name="v">Input matrix consisting of 4 256bit vectors</param>
/// <returns>256bit vector containing upper and lower scaled parts of the input matrix</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector256<float> Scale16x2_8x1(ReadOnlySpan<Vector256<float>> v)
{
Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter");
DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements");
var f025 = Vector256.Create(0.25f);
Vector256<float> left = Avx.Add(v[0], v[2]);
Vector256<float> right = Avx.Add(v[1], v[3]);
Vector256<float> avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025);
return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle();
}
#endif
}
}

12
src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Converter used to convert pixel data to jpeg spectral data.
/// </summary>
internal abstract class SpectralConverter
{
}
}

149
src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs

@ -0,0 +1,149 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Buffers;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <inheritdoc/>
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ComponentProcessor[] componentProcessors;
private readonly int pixelRowsPerStep;
private int pixelRowCounter;
private readonly Buffer2D<TPixel> pixelBuffer;
private readonly IMemoryOwner<float> redLane;
private readonly IMemoryOwner<float> greenLane;
private readonly IMemoryOwner<float> blueLane;
private readonly int alignedPixelWidth;
private readonly JpegColorConverterBase colorConverter;
public SpectralConverter(JpegFrame frame, Image<TPixel> image, Block8x8F[] dequantTables)
{
MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator;
// iteration data
int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);
int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height);
const int blockPixelHeight = 8;
this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight;
// pixel buffer of the image
this.pixelBuffer = image.GetRootFramePixelBuffer();
// component processors from spectral to Rgb24
const int blockPixelWidth = 8;
this.alignedPixelWidth = majorBlockWidth * blockPixelWidth;
var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep);
this.componentProcessors = new ComponentProcessor[frame.Components.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
Component component = frame.Components[i];
this.componentProcessors[i] = new ComponentProcessor(
allocator,
component,
postProcessorBufferSize,
dequantTables[component.QuantizationTableIndex]);
}
this.redLane = allocator.Allocate<float>(this.alignedPixelWidth, AllocationOptions.Clean);
this.greenLane = allocator.Allocate<float>(this.alignedPixelWidth, AllocationOptions.Clean);
this.blueLane = allocator.Allocate<float>(this.alignedPixelWidth, AllocationOptions.Clean);
// color converter from Rgb24 to YCbCr
this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8);
}
public void ConvertStrideBaseline()
{
// Codestyle suggests expression body but it
// also requires empty line before comments
// which looks ugly with expression bodies thus this warning disable
#pragma warning disable IDE0022
// Convert next pixel stride using single spectral `stride'
// Note that zero passing eliminates the need of virtual call
// from JpegComponentPostProcessor
this.ConvertStride(spectralStep: 0);
#pragma warning restore IDE0022
}
public void ConvertFull()
{
int steps = (int)Numerics.DivideCeil((uint)this.pixelBuffer.Height, (uint)this.pixelRowsPerStep);
for (int i = 0; i < steps; i++)
{
this.ConvertStride(i);
}
}
private void ConvertStride(int spectralStep)
{
int start = this.pixelRowCounter;
int end = start + this.pixelRowsPerStep;
int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1;
// Pixel strides must be padded with the last pixel of the stride
int paddingStartIndex = this.pixelBuffer.Width;
int paddedPixelsCount = this.alignedPixelWidth - this.pixelBuffer.Width;
Span<float> rLane = this.redLane.GetSpan();
Span<float> gLane = this.greenLane.GetSpan();
Span<float> bLane = this.blueLane.GetSpan();
for (int yy = start; yy < end; yy++)
{
int y = yy - this.pixelRowCounter;
// Unpack TPixel to r/g/b planes
int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex);
Span<TPixel> sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex);
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow);
rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]);
gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]);
bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]);
// Convert from rgb24 to target pixel type
var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y);
this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane);
}
// Convert pixels to spectral
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep);
}
this.pixelRowCounter = end;
}
/// <inheritdoc/>
public void Dispose()
{
foreach (ComponentProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
this.redLane.Dispose();
this.greenLane.Dispose();
this.blueLane.Dispose();
}
}
}

121
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs

@ -1,121 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter420<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel}, int)"/> call
/// </summary>
private const int PixelsPerSample = 16 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// The left Y component
/// </summary>
public Block8x8F YLeft;
/// <summary>
/// The left Y component
/// </summary>
public Block8x8F YRight;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 16x8 block to hold TPixel data
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal RGB block
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private readonly Configuration config;
public YCbCrForwardConverter420(ImageFrame<TPixel> frame)
{
// matrices would be filled during convert calls
this.YLeft = default;
this.YRight = default;
this.Cb = default;
this.Cr = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
// conversion vector fallback data
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.colorTables = RgbToYCbCrConverterLut.Create();
}
else
{
this.colorTables = default;
}
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(16, 8);
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
}
else
{
this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
}
}
}
}

122
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs

@ -1,122 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter444<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private readonly Configuration config;
public YCbCrForwardConverter444(ImageFrame<TPixel> frame)
{
// matrices would be filled during convert calls
this.Y = default;
this.Cb = default;
this.Cr = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
// conversion vector fallback data
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.colorTables = RgbToYCbCrConverterLut.Create();
}
else
{
this.colorTables = default;
}
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
else
{
this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
}
}
}

61
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal static class YCbCrForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public static void LoadAndStretchEdges(RowOctet<TPixel> source, Span<TPixel> dest, Point start, Size sampleSize, Size totalSize)
{
DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X));
DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y));
int width = Math.Min(sampleSize.Width, totalSize.Width - start.X);
int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y);
uint byteWidth = (uint)(width * Unsafe.SizeOf<TPixel>());
int remainderXCount = sampleSize.Width - width;
ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TPixel, byte>(dest));
int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf<TPixel>();
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source[y];
ref byte s = ref Unsafe.As<TPixel, byte>(ref row[start.X]);
ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
ref TPixel last = ref Unsafe.Add(ref Unsafe.As<byte, TPixel>(ref d), width - 1);
for (int x = 1; x <= remainderXCount; x++)
{
Unsafe.Add(ref last, x) = last;
}
}
int remainderYCount = sampleSize.Height - height;
if (remainderYCount == 0)
{
return;
}
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes);
for (int y = 1; y <= remainderYCount; y++)
{
ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y);
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes);
}
}
}
}

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs → src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs

@ -1,15 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Identifies the colorspace of a Jpeg image.
/// </summary>
internal enum JpegColorSpace
{
Undefined = 0,
/// <summary>
/// Color space with 1 component.
/// </summary>

15
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -176,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return quality < 50 ? (5000 / quality) : (200 - (quality * 2));
}
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
@ -188,6 +188,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return table;
}
public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable)
{
int scale = QualityToScale(quality);
Block8x8 table = default;
for (int j = 0; j < Block8x8.Size; j++)
{
int x = ((unscaledTable[j] * scale) + 50) / 100;
table[j] = (short)(uint)Numerics.Clamp(x, 1, 255);
}
return table;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleLuminanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable);

13
src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

@ -16,8 +16,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public int? Quality { get; set; }
/// <summary>
/// Gets the color type, that will be used to encode the image.
/// Gets or sets the component encoding mode.
/// </summary>
JpegColorType? ColorType { get; }
/// <remarks>
/// Interleaved encoding mode encodes all color components in a single scan.
/// Non-interleaved encoding mode encodes each color component in a separate scan.
/// </remarks>
public bool? Interleaved { get; set; }
/// <summary>
/// Gets or sets jpeg color for encoding.
/// </summary>
public JpegEncodingColor? ColorType { get; set; }
}
}

1
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
using JpegDecoderCore decoder = new(new() { GeneralOptions = options });

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

@ -500,6 +500,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Read on.
fileMarker = FindNextFileMarker(stream);
}
this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved;
}
/// <inheritdoc/>
@ -579,57 +581,58 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Returns the jpeg color type based on the colorspace and subsampling used.
/// </summary>
/// <returns>Jpeg color type.</returns>
private JpegColorType DeduceJpegColorType()
private JpegEncodingColor DeduceJpegColorType()
{
switch (this.ColorSpace)
{
case JpegColorSpace.Grayscale:
return JpegColorType.Luminance;
return JpegEncodingColor.Luminance;
case JpegColorSpace.RGB:
return JpegColorType.Rgb;
return JpegEncodingColor.Rgb;
case JpegColorSpace.YCbCr:
if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio444;
return JpegEncodingColor.YCbCrRatio444;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio420;
return JpegEncodingColor.YCbCrRatio422;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2)
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio422;
return JpegEncodingColor.YCbCrRatio420;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio411;
return JpegEncodingColor.YCbCrRatio411;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio410;
return JpegEncodingColor.YCbCrRatio410;
}
else
{
return JpegColorType.YCbCrRatio420;
return JpegEncodingColor.YCbCrRatio420;
}
case JpegColorSpace.Cmyk:
return JpegColorType.Cmyk;
return JpegEncodingColor.Cmyk;
case JpegColorSpace.Ycck:
return JpegEncodingColor.Ycck;
default:
return JpegColorType.YCbCrRatio420;
return JpegEncodingColor.YCbCrRatio420;
}
}
@ -1212,6 +1215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive;
remaining -= length;
@ -1422,7 +1426,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// selectorsCount*2 bytes: component index + huffman tables indices
stream.Read(this.temp, 0, selectorsBytes);
this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount;
this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount;
for (int i = 0; i < selectorsBytes; i += 2)
{
// 1 byte: Component id

25
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -13,11 +14,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{
/// <summary>
/// Backing field for <see cref="Quality"/>.
/// </summary>
private int? quality;
/// <inheritdoc/>
public int? Quality
{
get => this.quality;
set
{
if (value is < 1 or > 100)
{
throw new ArgumentException("Quality factor must be in [1..100] range.");
}
this.quality = value;
}
}
/// <inheritdoc/>
public int? Quality { get; set; }
public bool? Interleaved { get; set; }
/// <inheritdoc/>
public JpegColorType? ColorType { get; set; }
public JpegEncodingColor? ColorType { get; set; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.

196
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs

@ -0,0 +1,196 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Image encoder for writing an image to a stream as a jpeg.
/// </summary>
internal sealed unsafe partial class JpegEncoderCore
{
private static JpegFrameConfig[] CreateFrameConfigs()
{
var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.LuminanceDC);
var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.LuminanceAC);
var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.ChrominanceDC);
var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.ChrominanceAC);
var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable);
var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable);
var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[]
{
defaultLuminanceHuffmanDC,
defaultLuminanceHuffmanAC,
defaultChrominanceHuffmanDC,
defaultChrominanceHuffmanAC,
};
var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[]
{
defaultLuminanceQuantTable,
defaultChrominanceQuantTable,
};
return new JpegFrameConfig[]
{
// YCbCr 4:4:4
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio444,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
},
yCbCrHuffmanConfigs,
yCbCrQuantTableConfigs),
// YCbCr 4:2:2
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio422,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
},
yCbCrHuffmanConfigs,
yCbCrQuantTableConfigs),
// YCbCr 4:2:0
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio420,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
},
yCbCrHuffmanConfigs,
yCbCrQuantTableConfigs),
// YCbCr 4:1:1
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio411,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
},
yCbCrHuffmanConfigs,
yCbCrQuantTableConfigs),
// YCbCr 4:1:0
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio410,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1),
},
yCbCrHuffmanConfigs,
yCbCrQuantTableConfigs),
// Luminance
new JpegFrameConfig(
JpegColorSpace.Grayscale,
JpegEncodingColor.Luminance,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
},
new JpegHuffmanTableConfig[]
{
defaultLuminanceHuffmanDC,
defaultLuminanceHuffmanAC
},
new JpegQuantizationTableConfig[]
{
defaultLuminanceQuantTable
}),
// Rgb
new JpegFrameConfig(
JpegColorSpace.RGB,
JpegEncodingColor.Rgb,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
},
new JpegHuffmanTableConfig[]
{
defaultLuminanceHuffmanDC,
defaultLuminanceHuffmanAC
},
new JpegQuantizationTableConfig[]
{
defaultLuminanceQuantTable
})
{
AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown
},
// Cmyk
new JpegFrameConfig(
JpegColorSpace.Cmyk,
JpegEncodingColor.Cmyk,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
},
new JpegHuffmanTableConfig[]
{
defaultLuminanceHuffmanDC,
defaultLuminanceHuffmanAC
},
new JpegQuantizationTableConfig[]
{
defaultLuminanceQuantTable
})
{
AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown,
},
// YccK
new JpegFrameConfig(
JpegColorSpace.Ycck,
JpegEncodingColor.Ycck,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
},
new JpegHuffmanTableConfig[]
{
defaultLuminanceHuffmanDC,
defaultLuminanceHuffmanAC
},
new JpegQuantizationTableConfig[]
{
defaultLuminanceQuantTable
})
{
AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck,
},
};
}
}
}

552
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -8,7 +8,6 @@ using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -22,27 +21,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Image encoder for writing an image to a stream as a jpeg.
/// </summary>
internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals
internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
{
/// <summary>
/// The number of quantization tables.
/// The available encodable frame configs.
/// </summary>
private const int QuantizationTableCount = 2;
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[20];
/// <summary>
/// The quality, that will be used to encode the image.
/// </summary>
private readonly int? quality;
/// <summary>
/// Gets or sets the colorspace to use.
/// </summary>
private JpegColorType? colorType;
private readonly IJpegEncoderOptions options;
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
@ -54,14 +45,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="options">The options.</param>
public JpegEncoderCore(IJpegEncoderOptions options)
{
this.quality = options.Quality;
=> this.options = options;
if (IsSupportedColorType(options.ColorType))
{
this.colorType = options.ColorType;
}
}
public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4];
/// <summary>
/// Encode writes the image to the jpeg baseline format with the given options.
@ -84,72 +70,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
cancellationToken.ThrowIfCancellationRequested();
this.outputStream = stream;
ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata);
// If the color type was not specified by the user, preserve the color type of the input image.
if (!this.colorType.HasValue)
{
this.colorType = GetFallbackColorType(image);
}
// Compute number of components based on color type in options.
int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
ReadOnlySpan<byte> componentIds = this.GetComponentIds();
// TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
// Initialize the quantization tables.
this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true;
using var frame = new JpegFrame(image, frameConfig, interleaved);
// Write the Start Of Image marker.
this.WriteStartOfImage();
// Do not write APP0 marker for RGB colorspace.
if (this.colorType != JpegColorType.Rgb)
// Write APP0 marker
if (frameConfig.AdobeColorTransformMarkerFlag is null)
{
this.WriteJfifApplicationHeader(metadata);
}
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata);
if (this.colorType == JpegColorType.Rgb)
// Write APP14 marker with adobe color extension
else
{
// Write App14 marker to indicate RGB color space.
this.WriteApp14Marker();
this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value);
}
// Write the quantization tables.
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds);
this.WriteStartOfFrame(image.Width, image.Height, frameConfig);
// Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount);
var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream);
this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder);
// Write the scan header.
this.WriteStartOfScan(componentCount, componentIds);
// Write the quantization tables.
this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata);
// Write the scan compressed data.
switch (this.colorType)
{
case JpegColorType.YCbCrRatio444:
new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegColorType.YCbCrRatio420:
new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegColorType.Luminance:
new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
break;
case JpegColorType.Rgb:
new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
break;
default:
// all other non-supported color types are checked at the start of this method
break;
}
// Write scans with actual pixel data
using var spectralConverter = new SpectralConverter<TPixel>(frame, image, this.QuantizationTables);
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken);
// Write the End Of Image marker.
this.WriteEndOfImageMarker();
@ -157,75 +116,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Flush();
}
/// <summary>
/// If color type was not set, set it based on the given image.
/// Note, if there is no metadata and the image has multiple components this method
/// returns <see langword="null"/> defering the field assignment
/// to <see cref="InitQuantizationTables(int, JpegMetadata, out Block8x8F, out Block8x8F)"/>.
/// </summary>
private static JpegColorType? GetFallbackColorType<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// First inspect the image metadata.
JpegColorType? colorType = null;
JpegMetadata metadata = image.Metadata.GetJpegMetadata();
if (IsSupportedColorType(metadata.ColorType))
{
return metadata.ColorType;
}
// Secondly, inspect the pixel type.
// TODO: PixelTypeInfo should contain a component count!
bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
// We don't set multi-component color types here since we can set it based upon
// the quality in InitQuantizationTables.
if (isGrayscale)
{
colorType = JpegColorType.Luminance;
}
return colorType;
}
/// <summary>
/// Returns true, if the color type is supported by the encoder.
/// </summary>
/// <param name="colorType">The color type.</param>
/// <returns>true, if color type is supported.</returns>
private static bool IsSupportedColorType(JpegColorType? colorType)
=> colorType == JpegColorType.YCbCrRatio444
|| colorType == JpegColorType.YCbCrRatio420
|| colorType == JpegColorType.Luminance
|| colorType == JpegColorType.Rgb;
/// <summary>
/// Gets the component ids.
/// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3.
/// </summary>
/// <returns>The component Ids.</returns>
private ReadOnlySpan<byte> GetComponentIds() => this.colorType == JpegColorType.Rgb
? new ReadOnlySpan<byte>(new byte[] { 82, 71, 66 })
: new ReadOnlySpan<byte>(new byte[] { 1, 2, 3 });
/// <summary>
/// Writes data to "Define Quantization Tables" block for QuantIndex.
/// </summary>
/// <param name="dqt">The "Define Quantization Tables" block.</param>
/// <param name="offset">Offset in "Define Quantization Tables" block.</param>
/// <param name="i">The quantization index.</param>
/// <param name="quant">The quantization table to copy data from.</param>
private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant)
{
dqt[offset++] = (byte)i;
for (int j = 0; j < Block8x8F.Size; j++)
{
dqt[offset++] = (byte)quant[ZigZag.ZigZagOrder[j]];
}
}
/// <summary>
/// Write the start of image marker.
/// </summary>
@ -286,71 +176,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the Define Huffman Table marker and tables.
/// </summary>
/// <param name="componentCount">The number of components to write.</param>
private void WriteDefineHuffmanTables(int componentCount)
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder)
{
// This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
// and doesn't incur any allocation at all.
// Table identifiers.
ReadOnlySpan<byte> headers = new byte[]
if (tableConfigs is null)
{
0x00,
0x10,
0x01,
0x11
};
throw new ArgumentNullException(nameof(tableConfigs));
}
int markerlen = 2;
HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs;
if (componentCount == 1)
for (int i = 0; i < tableConfigs.Length; i++)
{
// Drop the Chrominance tables.
specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] };
}
for (int i = 0; i < specs.Length; i++)
{
ref HuffmanSpec s = ref specs[i];
markerlen += 1 + 16 + s.Values.Length;
markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length;
}
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
for (int i = 0; i < specs.Length; i++)
for (int i = 0; i < tableConfigs.Length; i++)
{
this.outputStream.WriteByte(headers[i]);
this.outputStream.Write(specs[i].Count);
this.outputStream.Write(specs[i].Values);
}
}
JpegHuffmanTableConfig tableConfig = tableConfigs[i];
/// <summary>
/// Writes the Define Quantization Marker and tables.
/// </summary>
private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable)
{
// Marker + quantization table lengths.
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size));
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
// Loop through and collect the tables as one array.
// This allows us to reduce the number of writes to the stream.
int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount;
byte[] dqt = new byte[dqtCount];
int offset = 0;
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref luminanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref chrominanceQuantTable);
int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex;
this.outputStream.WriteByte((byte)header);
this.outputStream.Write(tableConfig.Table.Count);
this.outputStream.Write(tableConfig.Table.Values);
this.outputStream.Write(dqt, 0, dqtCount);
scanEncoder.BuildHuffmanTable(tableConfig);
}
}
/// <summary>
/// Writes the APP14 marker to indicate the image is in RGB color space.
/// </summary>
private void WriteApp14Marker()
private void WriteApp14Marker(byte colorTransform)
{
this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + AdobeMarker.Length);
this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length);
// Identifier: ASCII "Adobe".
this.buffer[0] = 0x41;
@ -368,8 +227,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Flags1
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0);
// Transform byte, 0 in combination with three components means the image is in RGB colorspace.
this.buffer[11] = 0;
// Color transform byte
this.buffer[11] = colorTransform;
this.outputStream.Write(this.buffer.AsSpan(0, 12));
}
@ -385,8 +244,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return;
}
const int MaxBytesApp1 = 65533; // 64k - 2 padding bytes
const int MaxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header.
const int maxBytesApp1 = 65533; // 64k - 2 padding bytes
const int maxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header.
byte[] data = exifProfile.ToByteArray();
@ -396,27 +255,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// We can write up to a maximum of 64 data to the initial marker so calculate boundaries.
int exifMarkerLength = ProfileResolver.ExifMarker.Length;
int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length;
int remaining = exifMarkerLength + data.Length;
int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining;
int bytesToWrite = remaining > maxBytesApp1 ? maxBytesApp1 : remaining;
int app1Length = bytesToWrite + 2;
// Write the app marker, EXIF marker, and data
this.WriteApp1Header(app1Length);
this.outputStream.Write(ProfileResolver.ExifMarker);
this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker);
this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength);
remaining -= bytesToWrite;
// If the exif data exceeds 64K, write it in multiple APP1 Markers
for (int idx = MaxBytesWithExifId; idx < data.Length; idx += MaxBytesWithExifId)
for (int idx = maxBytesWithExifId; idx < data.Length; idx += maxBytesWithExifId)
{
bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining;
bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining;
app1Length = bytesToWrite + 2 + exifMarkerLength;
this.WriteApp1Header(app1Length);
// Write Exif00 marker
this.outputStream.Write(ProfileResolver.ExifMarker);
this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker);
// Write the exif data
this.outputStream.Write(data, idx, bytesToWrite);
@ -434,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void WriteIptcProfile(IptcProfile iptcProfile)
{
const int Max = 65533;
const int maxBytes = 65533;
if (iptcProfile is null || !iptcProfile.Values.Any())
{
return;
@ -447,19 +306,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return;
}
if (data.Length > Max)
if (data.Length > maxBytes)
{
throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes");
throw new ImageFormatException($"Iptc profile size exceeds limit of {maxBytes} bytes");
}
int app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length +
ProfileResolver.AdobeImageResourceBlockMarker.Length +
ProfileResolver.AdobeIptcMarker.Length +
int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length +
Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length +
Components.Decoder.ProfileResolver.AdobeIptcMarker.Length +
2 + 4 + data.Length;
this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13);
this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker);
this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker);
this.outputStream.Write(ProfileResolver.AdobeIptcMarker);
this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker);
this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker);
this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker);
this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even)
this.outputStream.WriteByte(0);
BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length);
@ -481,9 +340,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return;
}
const int XmpOverheadLength = 29;
const int Max = 65533;
const int MaxData = Max - XmpOverheadLength;
const int xmpOverheadLength = 29;
const int maxBytes = 65533;
const int maxData = maxBytes - xmpOverheadLength;
byte[] data = xmpProfile.Data;
@ -499,16 +358,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
int length = dataLength; // Number of bytes to write.
if (length > MaxData)
if (length > maxData)
{
length = MaxData;
length = maxData;
}
dataLength -= length;
int app1Length = 2 + ProfileResolver.XmpMarker.Length + length;
int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length;
this.WriteApp1Header(app1Length);
this.outputStream.Write(ProfileResolver.XmpMarker);
this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker);
this.outputStream.Write(data, offset, length);
offset += length;
@ -551,9 +410,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return;
}
const int IccOverheadLength = 14;
const int Max = 65533;
const int MaxData = Max - IccOverheadLength;
const int iccOverheadLength = 14;
const int maxBytes = 65533;
const int maxData = maxBytes - iccOverheadLength;
byte[] data = iccProfile.ToByteArray();
@ -564,9 +423,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Calculate the number of markers we'll need, rounding up of course.
int dataLength = data.Length;
int count = dataLength / MaxData;
int count = dataLength / maxData;
if (count * MaxData != dataLength)
if (count * maxData != dataLength)
{
count++;
}
@ -579,9 +438,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
int length = dataLength; // Number of bytes to write.
if (length > MaxData)
if (length > maxData)
{
length = MaxData;
length = maxData;
}
dataLength -= length;
@ -609,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[12] = (byte)current; // The position within the collection.
this.buffer[13] = (byte)count; // The total number of profiles.
this.outputStream.Write(this.buffer, 0, IccOverheadLength);
this.outputStream.Write(this.buffer, 0, iccOverheadLength);
this.outputStream.Write(data, offset, length);
current++;
@ -643,124 +502,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the Start Of Frame (Baseline) marker.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="componentCount">The number of components in a pixel.</param>
/// <param name="componentIds">The component Id's.</param>
private void WriteStartOfFrame(int width, int height, int componentCount, ReadOnlySpan<byte> componentIds)
private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame)
{
// This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
// and doesn't incur any allocation at all.
// "default" to 4:2:0
ReadOnlySpan<byte> subsamples = new byte[]
{
0x22,
0x11,
0x11
};
ReadOnlySpan<byte> chroma = new byte[]
{
0x00,
0x01,
0x01
};
if (this.colorType == JpegColorType.Luminance)
{
subsamples = new byte[]
{
0x11,
0x00,
0x00
};
}
else
{
switch (this.colorType)
{
case JpegColorType.YCbCrRatio444:
case JpegColorType.Rgb:
subsamples = new byte[]
{
0x11,
0x11,
0x11
};
if (this.colorType == JpegColorType.Rgb)
{
chroma = new byte[]
{
0x00,
0x00,
0x00
};
}
break;
case JpegColorType.YCbCrRatio420:
subsamples = new byte[]
{
0x22,
0x11,
0x11
};
break;
}
}
JpegComponentConfig[] components = frame.Components;
// Length (high byte, low byte), 8 + components * 3.
int markerlen = 8 + (3 * componentCount);
int markerlen = 8 + (3 * components.Length);
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen);
this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
this.buffer[1] = (byte)(height >> 8);
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[3] = (byte)(width >> 8);
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[5] = (byte)componentCount;
this.buffer[5] = (byte)components.Length;
for (int i = 0; i < componentCount; i++)
// Components data
for (int i = 0; i < components.Length; i++)
{
int i3 = 3 * i;
// Component ID.
Span<byte> bufferSpan = this.buffer.AsSpan(i3 + 6, 3);
bufferSpan[2] = chroma[i];
bufferSpan[1] = subsamples[i];
bufferSpan[0] = componentIds[i];
// Quantization table selector
bufferSpan[2] = (byte)components[i].QuantizatioTableIndex;
// Sampling factors
// 4 bits
int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor;
bufferSpan[1] = (byte)samplingFactors;
// Id
bufferSpan[0] = components[i].Id;
}
this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9);
this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9);
}
/// <summary>
/// Writes the StartOfScan marker.
/// </summary>
/// <param name="componentCount">The number of components in a pixel.</param>
/// <param name="componentIds">The componentId's.</param>
private void WriteStartOfScan(int componentCount, ReadOnlySpan<byte> componentIds)
private void WriteStartOfScan(Span<JpegComponentConfig> components)
{
// This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
// and doesn't incur any allocation at all.
ReadOnlySpan<byte> huffmanId = new byte[]
{
0x00,
0x11,
0x11
};
// Use the same DC/AC tables for all channels for RGB.
if (this.colorType == JpegColorType.Rgb)
{
huffmanId = new byte[]
{
0x00,
0x00,
0x00
};
}
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c",
// - the number of components "\x03",
@ -774,15 +555,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[1] = JpegConstants.Markers.SOS;
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
int sosSize = 6 + (2 * componentCount);
int sosSize = 6 + (2 * components.Length);
this.buffer[2] = 0x00;
this.buffer[3] = (byte)sosSize;
this.buffer[4] = (byte)componentCount; // Number of components in a scan
for (int i = 0; i < componentCount; i++)
this.buffer[4] = (byte)components.Length; // Number of components in a scan
// Components data
for (int i = 0; i < components.Length; i++)
{
int i2 = 2 * i;
this.buffer[i2 + 5] = componentIds[i]; // Component Id
this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table
// Id
this.buffer[i2 + 5] = components[i].Id;
// Table selectors
int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector;
this.buffer[i2 + 6] = (byte)tableSelectors;
}
this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.
@ -801,6 +589,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(this.buffer, 0, 2);
}
/// <summary>
/// Writes scans for given config.
/// </summary>
private void WriteHuffmanScans<TPixel>(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter<TPixel> spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
if (frame.Components.Length == 1)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(frameConfig.Components);
encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken);
}
else if (frame.Interleaved)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(frameConfig.Components);
encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken);
}
else
{
frame.AllocateComponents(fullScan: true);
spectralConverter.ConvertFull();
Span<JpegComponentConfig> components = frameConfig.Components;
for (int i = 0; i < frame.Components.Length; i++)
{
this.WriteStartOfScan(components.Slice(i, 1));
encoder.EncodeScanBaseline(frame.Components[i], cancellationToken);
}
}
}
/// <summary>
/// Writes the header for a marker with the given length.
/// </summary>
@ -817,54 +639,78 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Initializes quantization tables.
/// Writes the Define Quantization Marker and prepares tables for encoding.
/// </summary>
/// <remarks>
/// <para>
/// Zig-zag ordering is NOT applied to the resulting tables.
/// </para>
/// <para>
/// We take quality values in a hierarchical order:
/// 1. Check if encoder has set quality
/// 2. Check if metadata has set quality
/// 3. Take default quality value - 75
/// </para>
/// <list type = "number" >
/// <item>Check if encoder has set quality.</item>
/// <item>Check if metadata has set quality.</item>
/// <item>Take default quality value from <see cref="Quantization.DefaultQualityFactor"/></item>
/// </list>
/// </remarks>
/// <param name="componentCount">Color components count.</param>
/// <param name="configs">Quantization tables configs.</param>
/// <param name="optionsQuality">Optional quality value from the options.</param>
/// <param name="metadata">Jpeg metadata instance.</param>
/// <param name="luminanceQuantTable">Output luminance quantization table.</param>
/// <param name="chrominanceQuantTable">Output chrominance quantization table.</param>
private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable)
private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata)
{
int lumaQuality;
int chromaQuality;
if (this.quality.HasValue)
{
lumaQuality = this.quality.Value;
chromaQuality = this.quality.Value;
}
else
{
lumaQuality = metadata.LuminanceQuality;
chromaQuality = metadata.ChrominanceQuality;
}
int dataLen = configs.Length * (1 + Block8x8.Size);
// Marker + quantization table lengths.
int markerlen = 2 + dataLen;
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
// Luminance
lumaQuality = Numerics.Clamp(lumaQuality, 1, 100);
luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);
byte[] buffer = new byte[dataLen];
int offset = 0;
Block8x8F workspaceBlock = default;
// Chrominance
chrominanceQuantTable = default;
if (componentCount > 1)
for (int i = 0; i < configs.Length; i++)
{
chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
JpegQuantizationTableConfig config = configs[i];
int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata);
Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table);
if (!this.colorType.HasValue)
// write to the output stream
buffer[offset++] = (byte)config.DestinationIndex;
for (int j = 0; j < Block8x8.Size; j++)
{
this.colorType = chromaQuality >= 91 ? JpegColorType.YCbCrRatio444 : JpegColorType.YCbCrRatio420;
buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]];
}
// apply FDCT multipliers and inject to the destination index
workspaceBlock.LoadFrom(ref scaledTable);
FloatingPointDCT.AdjustToFDCT(ref workspaceBlock);
this.QuantizationTables[config.DestinationIndex] = workspaceBlock;
}
// write filled buffer to the stream
this.outputStream.Write(buffer);
static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch
{
0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor,
1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor,
_ => encoderQuality ?? metadata.Quality,
};
}
private JpegFrameConfig GetFrameConfig(JpegMetadata metadata)
{
JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420;
JpegFrameConfig frameConfig = Array.Find(
FrameConfigs,
cfg => cfg.EncodingColor == color);
if (frameConfig == null)
{
throw new ArgumentException(nameof(color));
}
return frameConfig;
}
}
}

15
src/ImageSharp/Formats/Jpeg/JpegColorType.cs → src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Provides enumeration of available JPEG color types.
/// </summary>
public enum JpegColorType : byte
public enum JpegEncodingColor : byte
{
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
@ -25,24 +25,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution.
///
/// Note: Not supported by the encoder.
/// </summary>
YCbCrRatio422 = 2,
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered.
///
/// Note: Not supported by the encoder.
/// </summary>
YCbCrRatio411 = 3,
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// This ratio uses half of the vertical and one-fourth the horizontal color resolutions.
///
/// Note: Not supported by the encoder.
/// </summary>
YCbCrRatio410 = 4,
@ -58,9 +52,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing.
///
/// Note: Not supported by the encoder.
/// </summary>
Cmyk = 7,
/// <summary>
/// YCCK colorspace (Y, Cb, Cr, and key black).
/// </summary>
Ycck = 8,
}
}

86
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -11,16 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public class JpegMetadata : IDeepCloneable
{
/// <summary>
/// Backing field for <see cref="LuminanceQuality"/>
/// </summary>
private int? luminanceQuality;
/// <summary>
/// Backing field for <see cref="ChrominanceQuality"/>
/// </summary>
private int? chrominanceQuality;
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary>
@ -36,8 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
this.ColorType = other.ColorType;
this.luminanceQuality = other.luminanceQuality;
this.chrominanceQuality = other.chrominanceQuality;
this.LuminanceQuality = other.LuminanceQuality;
this.ChrominanceQuality = other.ChrominanceQuality;
}
/// <summary>
@ -47,11 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int LuminanceQuality
{
get => this.luminanceQuality ?? Quantization.DefaultQualityFactor;
set => this.luminanceQuality = value;
}
internal int? LuminanceQuality { get; set; }
/// <summary>
/// Gets or sets the jpeg chrominance quality.
@ -60,55 +46,61 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int ChrominanceQuality
{
get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor;
set => this.chrominanceQuality = value;
}
internal int? ChrominanceQuality { get; set; }
/// <summary>
/// Gets or sets the encoded quality.
/// Gets the encoded quality.
/// </summary>
/// <remarks>
/// Note that jpeg image can have different quality for luminance and chrominance components.
/// This property returns maximum value of luma/chroma qualities.
/// This property returns maximum value of luma/chroma qualities if both are present.
/// </remarks>
public int Quality
{
get
{
// Jpeg always has a luminance table thus it must have a luminance quality derived from it
if (!this.luminanceQuality.HasValue)
if (this.LuminanceQuality.HasValue)
{
return Quantization.DefaultQualityFactor;
}
if (this.ChrominanceQuality.HasValue)
{
return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value);
}
int lumaQuality = this.luminanceQuality.Value;
// Jpeg might not have a chrominance table - return luminance quality (grayscale images)
if (!this.chrominanceQuality.HasValue)
{
return lumaQuality;
return this.LuminanceQuality.Value;
}
else
{
if (this.ChrominanceQuality.HasValue)
{
return this.ChrominanceQuality.Value;
}
int chromaQuality = this.chrominanceQuality.Value;
// Theoretically, luma quality would always be greater or equal to chroma quality
// But we've already encountered images which can have higher quality of chroma components
return Math.Max(lumaQuality, chromaQuality);
}
set
{
this.LuminanceQuality = value;
this.ChrominanceQuality = value;
return Quantization.DefaultQualityFactor;
}
}
}
/// <summary>
/// Gets or sets the color type.
/// Gets the color type.
/// </summary>
public JpegEncodingColor? ColorType { get; internal set; }
/// <summary>
/// Gets the component encoding mode.
/// </summary>
public JpegColorType? ColorType { get; set; }
/// <remarks>
/// Interleaved encoding mode encodes all color components in a single scan.
/// Non-interleaved encoding mode encodes each color component in a separate scan.
/// </remarks>
public bool? Interleaved { get; internal set; }
/// <summary>
/// Gets the scan encoding mode.
/// </summary>
/// <remarks>
/// Progressive jpeg images encode component data across multiple scans.
/// </remarks>
public bool? Progressive { get; internal set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetadata(this);

2
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
var image = Image.LoadPixelData<Rgb24>(rows, width, height);
image.Save(memoryStream, new JpegEncoder()
{
ColorType = JpegColorType.Rgb
ColorType = JpegEncodingColor.Rgb
});
memoryStream.Position = 0;
memoryStream.WriteTo(this.Output);

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

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors

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

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Spectral converter for YCbCr TIFF's which use the JPEG compression.
/// The jpeg data should be always treated as RGB color space.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
internal sealed class RgbJpegSpectralConverter<TPixel> : SpectralConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="RgbJpegSpectralConverter{TPixel}"/> class.
/// This Spectral converter will always convert the pixel data to RGB color.
/// </summary>
/// <param name="configuration">The configuration.</param>
public RgbJpegSpectralConverter(Configuration configuration)
: base(configuration)
{
}
/// <inheritdoc/>
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision);
}
}

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;

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

@ -417,6 +417,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return;
case TiffPhotometricInterpretation.Rgb:
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType))
{
// Invalid compression / bits per pixel combination, fallback to no compression.
compression = DefaultCompression;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor);
return;
}

8
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs

@ -36,6 +36,14 @@ namespace SixLabors.ImageSharp.PixelFormats
SimdUtils.PackFromRgbPlanes(configuration, redChannel, greenChannel, blueChannel, destination);
}
/// <inheritdoc />
internal override void UnpackIntoRgbPlanes(
Span<float> redChannel,
Span<float> greenChannel,
Span<float> blueChannel,
ReadOnlySpan<Rgb24> source)
=> SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source);
}
}
}

31
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -198,6 +198,37 @@ namespace SixLabors.ImageSharp.PixelFormats
}
}
/// <summary>
/// Bulk operation that unpacks pixels from <paramref name="source"/>
/// into 3 seperate RGB channels. The destination must have a padding of 3.
/// </summary>
/// <param name="redChannel">A <see cref="ReadOnlySpan{T}"/> to the red values.</param>
/// <param name="greenChannel">A <see cref="ReadOnlySpan{T}"/> to the green values.</param>
/// <param name="blueChannel">A <see cref="ReadOnlySpan{T}"/> to the blue values.</param>
/// <param name="source">A <see cref="Span{T}"/> to the destination pixels.</param>
internal virtual void UnpackIntoRgbPlanes(
Span<float> redChannel,
Span<float> greenChannel,
Span<float> blueChannel,
ReadOnlySpan<TPixel> source)
{
int count = redChannel.Length;
Rgba32 rgba32 = default;
ref float r = ref MemoryMarshal.GetReference(redChannel);
ref float g = ref MemoryMarshal.GetReference(greenChannel);
ref float b = ref MemoryMarshal.GetReference(blueChannel);
ref TPixel src = ref MemoryMarshal.GetReference(source);
for (int i = 0; i < count; i++)
{
Unsafe.Add(ref src, i).ToRgba32(ref rgba32);
Unsafe.Add(ref r, i) = rgba32.R;
Unsafe.Add(ref g, i) = rgba32.G;
Unsafe.Add(ref b, i) = rgba32.B;
}
}
[MethodImpl(InliningOptions.ShortMethod)]
internal static void GuardPackFromRgbPlanes(ReadOnlySpan<byte> greenChannel, ReadOnlySpan<byte> blueChannel, Span<TPixel> destination, int count)
{

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs

@ -2,7 +2,7 @@
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromCmykScalar(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInplace(values);
}
[Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromCmykVector(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.CmykVector(8).ConvertToRgbInplace(values);
}
#if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromCmykAvx(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values);
}
#endif
}

6
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs

@ -2,7 +2,7 @@
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromGrayscaleScalar(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.GrayscaleScalar(8).ConvertToRgbInplace(values);
}
#if SUPPORTS_RUNTIME_INTRINSICS
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromGrayscaleAvx(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values);
}
#endif
}

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs

@ -2,7 +2,7 @@
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromRgbScalar(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInplace(values);
}
[Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromRgbVector(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.RgbVector(8).ConvertToRgbInplace(values);
}
#if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromRgbAvx(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.RgbAvx(8).ConvertToRgbInplace(values);
}
#endif
}

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs

@ -2,7 +2,7 @@
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYCbCrScalar(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInplace(values);
}
[Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYCbCrVector(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInplace(values);
}
#if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYCbCrAvx(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values);
}
#endif
}

56
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs

@ -1,56 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder
{
public class YCbCrForwardConverterBenchmark
{
private RgbToYCbCrConverterLut converter;
private Rgb24[] data;
[GlobalSetup]
public void Setup()
{
this.converter = RgbToYCbCrConverterLut.Create();
var r = new Random(42);
this.data = new Rgb24[64];
var d = new byte[3];
for (int i = 0; i < this.data.Length; i++)
{
r.NextBytes(d);
this.data[i] = new Rgb24(d[0], d[1], d[2]);
}
}
[Benchmark(Baseline = true)]
public void ConvertLut()
{
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
this.converter.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr);
}
[Benchmark]
public void ConvertVectorized()
{
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr);
}
}
}
}

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs

@ -2,7 +2,7 @@
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYccKScalar(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInplace(values);
}
[Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYccKVector(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.YccKVector(8).ConvertToRgbInplace(values);
}
#if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYccKAvx(8).ConvertToRgbInplace(values);
new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values);
}
#endif
}

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.imageImageSharp = Image.Load<Rgba32>(imageBinaryStream);
this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 };
this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingColor.YCbCrRatio420 };
this.destinationStream = new MemoryStream();
}

48
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs

@ -22,14 +22,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
// No metadata
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
public static IEnumerable<JpegColorType> ColorSpaceValues =>
new[] { JpegColorType.Luminance, JpegColorType.Rgb, JpegColorType.YCbCrRatio420, JpegColorType.YCbCrRatio444 };
public static IEnumerable<JpegEncodingColor> ColorSpaceValues => new[]
{
JpegEncodingColor.Luminance,
JpegEncodingColor.Rgb,
JpegEncodingColor.YCbCrRatio420,
JpegEncodingColor.YCbCrRatio444,
};
[Params(75, 90, 100)]
public int Quality;
[ParamsSource(nameof(ColorSpaceValues), Priority = -100)]
public JpegColorType TargetColorSpace;
public JpegEncodingColor TargetColorSpace;
private Image<Rgb24> bmpCore;
private JpegEncoder encoder;
@ -41,7 +46,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgb24>(imageBinaryStream);
this.encoder = new JpegEncoder { Quality = this.Quality, ColorType = this.TargetColorSpace };
this.encoder = new JpegEncoder
{
Quality = this.Quality,
ColorType = this.TargetColorSpace,
Interleaved = true,
};
this.destinationStream = new MemoryStream();
}
@ -67,23 +77,23 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
.NET SDK=6.0.202
[Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
| Method | TargetColorSpace | Quality | Mean | Error | StdDev |
|---------- |----------------- |-------- |----------:|----------:|----------:|
| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms |
| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms |
| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms |
| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms |
| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms |
| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms |
| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms |
| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms |
| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms |
| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms |
| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms |
| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms |
| Benchmark | Luminance | 75 | 4.618 ms | 0.0263 ms | 0.0233 ms |
| Benchmark | Rgb | 75 | 12.543 ms | 0.0650 ms | 0.0608 ms |
| Benchmark | YCbCrRatio420 | 75 | 6.639 ms | 0.0778 ms | 0.1256 ms |
| Benchmark | YCbCrRatio444 | 75 | 8.590 ms | 0.0570 ms | 0.0505 ms |
| Benchmark | Luminance | 90 | 4.902 ms | 0.0307 ms | 0.0288 ms |
| Benchmark | Rgb | 90 | 13.447 ms | 0.0468 ms | 0.0415 ms |
| Benchmark | YCbCrRatio420 | 90 | 7.218 ms | 0.0586 ms | 0.0548 ms |
| Benchmark | YCbCrRatio444 | 90 | 9.150 ms | 0.0779 ms | 0.0729 ms |
| Benchmark | Luminance | 100 | 6.731 ms | 0.0325 ms | 0.0304 ms |
| Benchmark | Rgb | 100 | 19.831 ms | 0.1009 ms | 0.0788 ms |
| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0423 ms | 0.0396 ms |
| Benchmark | YCbCrRatio444 | 100 | 15.345 ms | 0.3276 ms | 0.3065 ms |
*/

240
tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs

@ -1,240 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Benchmarks
{
public partial class RgbToYCbCr
{
/// <summary>
/// Scaled integer RGBA to YCbCr lookup tables
/// </summary>
private static class LookupTables
{
public static readonly int[] Y0 =
{
0, 306, 612, 918, 1224, 1530, 1836, 2142, 2448, 2754, 3060, 3366, 3672, 3978, 4284,
4590, 4896, 5202, 5508, 5814, 6120, 6426, 6732, 7038, 7344, 7650, 7956, 8262, 8568,
8874, 9180, 9486, 9792, 10098, 10404, 10710, 11016, 11322, 11628, 11934, 12240,
12546, 12852, 13158, 13464, 13770, 14076, 14382, 14688, 14994, 15300, 15606, 15912,
16218, 16524, 16830, 17136, 17442, 17748, 18054, 18360, 18666, 18972, 19278, 19584,
19890, 20196, 20502, 20808, 21114, 21420, 21726, 22032, 22338, 22644, 22950, 23256,
23562, 23868, 24174, 24480, 24786, 25092, 25398, 25704, 26010, 26316, 26622, 26928,
27234, 27540, 27846, 28152, 28458, 28764, 29070, 29376, 29682, 29988, 30294, 30600,
30906, 31212, 31518, 31824, 32130, 32436, 32742, 33048, 33354, 33660, 33966, 34272,
34578, 34884, 35190, 35496, 35802, 36108, 36414, 36720, 37026, 37332, 37638, 37944,
38250, 38556, 38862, 39168, 39474, 39780, 40086, 40392, 40698, 41004, 41310, 41616,
41922, 42228, 42534, 42840, 43146, 43452, 43758, 44064, 44370, 44676, 44982, 45288,
45594, 45900, 46206, 46512, 46818, 47124, 47430, 47736, 48042, 48348, 48654, 48960,
49266, 49572, 49878, 50184, 50490, 50796, 51102, 51408, 51714, 52020, 52326, 52632,
52938, 53244, 53550, 53856, 54162, 54468, 54774, 55080, 55386, 55692, 55998, 56304,
56610, 56916, 57222, 57528, 57834, 58140, 58446, 58752, 59058, 59364, 59670, 59976,
60282, 60588, 60894, 61200, 61506, 61812, 62118, 62424, 62730, 63036, 63342, 63648,
63954, 64260, 64566, 64872, 65178, 65484, 65790, 66096, 66402, 66708, 67014, 67320,
67626, 67932, 68238, 68544, 68850, 69156, 69462, 69768, 70074, 70380, 70686, 70992,
71298, 71604, 71910, 72216, 72522, 72828, 73134, 73440, 73746, 74052, 74358, 74664,
74970, 75276, 75582, 75888, 76194, 76500, 76806, 77112, 77418, 77724, 78030
};
public static readonly int[] Y1 =
{
0, 601, 1202, 1803, 2404, 3005, 3606, 4207, 4808, 5409, 6010, 6611, 7212, 7813, 8414,
9015, 9616, 10217, 10818, 11419, 12020, 12621, 13222, 13823, 14424, 15025, 15626,
16227, 16828, 17429, 18030, 18631, 19232, 19833, 20434, 21035, 21636, 22237, 22838,
23439, 24040, 24641, 25242, 25843, 26444, 27045, 27646, 28247, 28848, 29449, 30050,
30651, 31252, 31853, 32454, 33055, 33656, 34257, 34858, 35459, 36060, 36661, 37262,
37863, 38464, 39065, 39666, 40267, 40868, 41469, 42070, 42671, 43272, 43873, 44474,
45075, 45676, 46277, 46878, 47479, 48080, 48681, 49282, 49883, 50484, 51085, 51686,
52287, 52888, 53489, 54090, 54691, 55292, 55893, 56494, 57095, 57696, 58297, 58898,
59499, 60100, 60701, 61302, 61903, 62504, 63105, 63706, 64307, 64908, 65509, 66110,
66711, 67312, 67913, 68514, 69115, 69716, 70317, 70918, 71519, 72120, 72721, 73322,
73923, 74524, 75125, 75726, 76327, 76928, 77529, 78130, 78731, 79332, 79933, 80534,
81135, 81736, 82337, 82938, 83539, 84140, 84741, 85342, 85943, 86544, 87145, 87746,
88347, 88948, 89549, 90150, 90751, 91352, 91953, 92554, 93155, 93756, 94357, 94958,
95559, 96160, 96761, 97362, 97963, 98564, 99165, 99766, 100367, 100968, 101569,
102170, 102771, 103372, 103973, 104574, 105175, 105776, 106377, 106978, 107579,
108180, 108781, 109382, 109983, 110584, 111185, 111786, 112387, 112988, 113589,
114190, 114791, 115392, 115993, 116594, 117195, 117796, 118397, 118998, 119599,
120200, 120801, 121402, 122003, 122604, 123205, 123806, 124407, 125008, 125609,
126210, 126811, 127412, 128013, 128614, 129215, 129816, 130417, 131018, 131619,
132220, 132821, 133422, 134023, 134624, 135225, 135826, 136427, 137028, 137629,
138230, 138831, 139432, 140033, 140634, 141235, 141836, 142437, 143038, 143639,
144240, 144841, 145442, 146043, 146644, 147245, 147846, 148447, 149048, 149649,
150250, 150851, 151452, 152053, 152654, 153255
};
public static readonly int[] Y2 =
{
0, 117, 234, 351, 468, 585, 702, 819, 936, 1053, 1170, 1287, 1404, 1521, 1638, 1755,
1872, 1989, 2106, 2223, 2340, 2457, 2574, 2691, 2808, 2925, 3042, 3159, 3276, 3393,
3510, 3627, 3744, 3861, 3978, 4095, 4212, 4329, 4446, 4563, 4680, 4797, 4914, 5031,
5148, 5265, 5382, 5499, 5616, 5733, 5850, 5967, 6084, 6201, 6318, 6435, 6552, 6669,
6786, 6903, 7020, 7137, 7254, 7371, 7488, 7605, 7722, 7839, 7956, 8073, 8190, 8307,
8424, 8541, 8658, 8775, 8892, 9009, 9126, 9243, 9360, 9477, 9594, 9711, 9828, 9945,
10062, 10179, 10296, 10413, 10530, 10647, 10764, 10881, 10998, 11115, 11232, 11349,
11466, 11583, 11700, 11817, 11934, 12051, 12168, 12285, 12402, 12519, 12636, 12753,
12870, 12987, 13104, 13221, 13338, 13455, 13572, 13689, 13806, 13923, 14040, 14157,
14274, 14391, 14508, 14625, 14742, 14859, 14976, 15093, 15210, 15327, 15444, 15561,
15678, 15795, 15912, 16029, 16146, 16263, 16380, 16497, 16614, 16731, 16848, 16965,
17082, 17199, 17316, 17433, 17550, 17667, 17784, 17901, 18018, 18135, 18252, 18369,
18486, 18603, 18720, 18837, 18954, 19071, 19188, 19305, 19422, 19539, 19656, 19773,
19890, 20007, 20124, 20241, 20358, 20475, 20592, 20709, 20826, 20943, 21060, 21177,
21294, 21411, 21528, 21645, 21762, 21879, 21996, 22113, 22230, 22347, 22464, 22581,
22698, 22815, 22932, 23049, 23166, 23283, 23400, 23517, 23634, 23751, 23868, 23985,
24102, 24219, 24336, 24453, 24570, 24687, 24804, 24921, 25038, 25155, 25272, 25389,
25506, 25623, 25740, 25857, 25974, 26091, 26208, 26325, 26442, 26559, 26676, 26793,
26910, 27027, 27144, 27261, 27378, 27495, 27612, 27729, 27846, 27963, 28080, 28197,
28314, 28431, 28548, 28665, 28782, 28899, 29016, 29133, 29250, 29367, 29484, 29601,
29718, 29835
};
public static readonly int[] Cb0 =
{
0, -172, -344, -516, -688, -860, -1032, -1204, -1376, -1548, -1720, -1892,
-2064, -2236, -2408, -2580, -2752, -2924, -3096, -3268, -3440, -3612,
-3784, -3956, -4128, -4300, -4472, -4644, -4816, -4988, -5160, -5332,
-5504, -5676, -5848, -6020, -6192, -6364, -6536, -6708, -6880, -7052,
-7224, -7396, -7568, -7740, -7912, -8084, -8256, -8428, -8600, -8772,
-8944, -9116, -9288, -9460, -9632, -9804, -9976, -10148, -10320, -10492,
-10664, -10836, -11008, -11180, -11352, -11524, -11696, -11868, -12040,
-12212, -12384, -12556, -12728, -12900, -13072, -13244, -13416, -13588,
-13760, -13932, -14104, -14276, -14448, -14620, -14792, -14964, -15136,
-15308, -15480, -15652, -15824, -15996, -16168, -16340, -16512, -16684,
-16856, -17028, -17200, -17372, -17544, -17716, -17888, -18060, -18232,
-18404, -18576, -18748, -18920, -19092, -19264, -19436, -19608, -19780,
-19952, -20124, -20296, -20468, -20640, -20812, -20984, -21156, -21328,
-21500, -21672, -21844, -22016, -22188, -22360, -22532, -22704, -22876,
-23048, -23220, -23392, -23564, -23736, -23908, -24080, -24252, -24424,
-24596, -24768, -24940, -25112, -25284, -25456, -25628, -25800, -25972,
-26144, -26316, -26488, -26660, -26832, -27004, -27176, -27348, -27520,
-27692, -27864, -28036, -28208, -28380, -28552, -28724, -28896, -29068,
-29240, -29412, -29584, -29756, -29928, -30100, -30272, -30444, -30616,
-30788, -30960, -31132, -31304, -31476, -31648, -31820, -31992, -32164,
-32336, -32508, -32680, -32852, -33024, -33196, -33368, -33540, -33712,
-33884, -34056, -34228, -34400, -34572, -34744, -34916, -35088, -35260,
-35432, -35604, -35776, -35948, -36120, -36292, -36464, -36636, -36808,
-36980, -37152, -37324, -37496, -37668, -37840, -38012, -38184, -38356,
-38528, -38700, -38872, -39044, -39216, -39388, -39560, -39732, -39904,
-40076, -40248, -40420, -40592, -40764, -40936, -41108, -41280, -41452,
-41624, -41796, -41968, -42140, -42312, -42484, -42656, -42828, -43000,
-43172, -43344, -43516, -43688, -43860
};
public static readonly int[] Cb1 =
{
0, 339, 678, 1017, 1356, 1695, 2034, 2373, 2712, 3051, 3390, 3729, 4068,
4407, 4746, 5085, 5424, 5763, 6102, 6441, 6780, 7119, 7458, 7797, 8136,
8475, 8814, 9153, 9492, 9831, 10170, 10509, 10848, 11187, 11526, 11865,
12204, 12543, 12882, 13221, 13560, 13899, 14238, 14577, 14916, 15255,
15594, 15933, 16272, 16611, 16950, 17289, 17628, 17967, 18306, 18645,
18984, 19323, 19662, 20001, 20340, 20679, 21018, 21357, 21696, 22035,
22374, 22713, 23052, 23391, 23730, 24069, 24408, 24747, 25086, 25425,
25764, 26103, 26442, 26781, 27120, 27459, 27798, 28137, 28476, 28815,
29154, 29493, 29832, 30171, 30510, 30849, 31188, 31527, 31866, 32205,
32544, 32883, 33222, 33561, 33900, 34239, 34578, 34917, 35256, 35595,
35934, 36273, 36612, 36951, 37290, 37629, 37968, 38307, 38646, 38985,
39324, 39663, 40002, 40341, 40680, 41019, 41358, 41697, 42036, 42375,
42714, 43053, 43392, 43731, 44070, 44409, 44748, 45087, 45426, 45765,
46104, 46443, 46782, 47121, 47460, 47799, 48138, 48477, 48816, 49155,
49494, 49833, 50172, 50511, 50850, 51189, 51528, 51867, 52206, 52545,
52884, 53223, 53562, 53901, 54240, 54579, 54918, 55257, 55596, 55935,
56274, 56613, 56952, 57291, 57630, 57969, 58308, 58647, 58986, 59325,
59664, 60003, 60342, 60681, 61020, 61359, 61698, 62037, 62376, 62715,
63054, 63393, 63732, 64071, 64410, 64749, 65088, 65427, 65766, 66105,
66444, 66783, 67122, 67461, 67800, 68139, 68478, 68817, 69156, 69495,
69834, 70173, 70512, 70851, 71190, 71529, 71868, 72207, 72546, 72885,
73224, 73563, 73902, 74241, 74580, 74919, 75258, 75597, 75936, 76275,
76614, 76953, 77292, 77631, 77970, 78309, 78648, 78987, 79326, 79665,
80004, 80343, 80682, 81021, 81360, 81699, 82038, 82377, 82716, 83055,
83394, 83733, 84072, 84411, 84750, 85089, 85428, 85767, 86106, 86445
};
public static readonly int[] Cb2Cr0 =
{
0, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4096, 4608, 5120, 5632, 6144,
6656, 7168, 7680, 8192, 8704, 9216, 9728, 10240, 10752, 11264, 11776,
12288, 12800, 13312, 13824, 14336, 14848, 15360, 15872, 16384, 16896,
17408, 17920, 18432, 18944, 19456, 19968, 20480, 20992, 21504, 22016,
22528, 23040, 23552, 24064, 24576, 25088, 25600, 26112, 26624, 27136,
27648, 28160, 28672, 29184, 29696, 30208, 30720, 31232, 31744, 32256,
32768, 33280, 33792, 34304, 34816, 35328, 35840, 36352, 36864, 37376,
37888, 38400, 38912, 39424, 39936, 40448, 40960, 41472, 41984, 42496,
43008, 43520, 44032, 44544, 45056, 45568, 46080, 46592, 47104, 47616,
48128, 48640, 49152, 49664, 50176, 50688, 51200, 51712, 52224, 52736,
53248, 53760, 54272, 54784, 55296, 55808, 56320, 56832, 57344, 57856,
58368, 58880, 59392, 59904, 60416, 60928, 61440, 61952, 62464, 62976,
63488, 64000, 64512, 65024, 65536, 66048, 66560, 67072, 67584, 68096,
68608, 69120, 69632, 70144, 70656, 71168, 71680, 72192, 72704, 73216,
73728, 74240, 74752, 75264, 75776, 76288, 76800, 77312, 77824, 78336,
78848, 79360, 79872, 80384, 80896, 81408, 81920, 82432, 82944, 83456,
83968, 84480, 84992, 85504, 86016, 86528, 87040, 87552, 88064, 88576,
89088, 89600, 90112, 90624, 91136, 91648, 92160, 92672, 93184, 93696,
94208, 94720, 95232, 95744, 96256, 96768, 97280, 97792, 98304, 98816,
99328, 99840, 100352, 100864, 101376, 101888, 102400, 102912, 103424,
103936, 104448, 104960, 105472, 105984, 106496, 107008, 107520, 108032,
108544, 109056, 109568, 110080, 110592, 111104, 111616, 112128, 112640,
113152, 113664, 114176, 114688, 115200, 115712, 116224, 116736, 117248,
117760, 118272, 118784, 119296, 119808, 120320, 120832, 121344, 121856,
122368, 122880, 123392, 123904, 124416, 124928, 125440, 125952, 126464,
126976, 127488, 128000, 128512, 129024, 129536, 130048, 130560
};
public static readonly int[] Cr1 =
{
0, 429, 858, 1287, 1716, 2145, 2574, 3003, 3432, 3861, 4290, 4719, 5148,
5577, 6006, 6435, 6864, 7293, 7722, 8151, 8580, 9009, 9438, 9867, 10296,
10725, 11154, 11583, 12012, 12441, 12870, 13299, 13728, 14157, 14586,
15015, 15444, 15873, 16302, 16731, 17160, 17589, 18018, 18447, 18876,
19305, 19734, 20163, 20592, 21021, 21450, 21879, 22308, 22737, 23166,
23595, 24024, 24453, 24882, 25311, 25740, 26169, 26598, 27027, 27456,
27885, 28314, 28743, 29172, 29601, 30030, 30459, 30888, 31317, 31746,
32175, 32604, 33033, 33462, 33891, 34320, 34749, 35178, 35607, 36036,
36465, 36894, 37323, 37752, 38181, 38610, 39039, 39468, 39897, 40326,
40755, 41184, 41613, 42042, 42471, 42900, 43329, 43758, 44187, 44616,
45045, 45474, 45903, 46332, 46761, 47190, 47619, 48048, 48477, 48906,
49335, 49764, 50193, 50622, 51051, 51480, 51909, 52338, 52767, 53196,
53625, 54054, 54483, 54912, 55341, 55770, 56199, 56628, 57057, 57486,
57915, 58344, 58773, 59202, 59631, 60060, 60489, 60918, 61347, 61776,
62205, 62634, 63063, 63492, 63921, 64350, 64779, 65208, 65637, 66066,
66495, 66924, 67353, 67782, 68211, 68640, 69069, 69498, 69927, 70356,
70785, 71214, 71643, 72072, 72501, 72930, 73359, 73788, 74217, 74646,
75075, 75504, 75933, 76362, 76791, 77220, 77649, 78078, 78507, 78936,
79365, 79794, 80223, 80652, 81081, 81510, 81939, 82368, 82797, 83226,
83655, 84084, 84513, 84942, 85371, 85800, 86229, 86658, 87087, 87516,
87945, 88374, 88803, 89232, 89661, 90090, 90519, 90948, 91377, 91806,
92235, 92664, 93093, 93522, 93951, 94380, 94809, 95238, 95667, 96096,
96525, 96954, 97383, 97812, 98241, 98670, 99099, 99528, 99957, 100386,
100815, 101244, 101673, 102102, 102531, 102960, 103389, 103818, 104247,
104676, 105105, 105534, 105963, 106392, 106821, 107250, 107679, 108108,
108537, 108966, 109395
};
public static readonly int[] Cr2 =
{
0, 83, 166, 249, 332, 415, 498, 581, 664, 747, 830, 913, 996, 1079, 1162,
1245, 1328, 1411, 1494, 1577, 1660, 1743, 1826, 1909, 1992, 2075, 2158,
2241, 2324, 2407, 2490, 2573, 2656, 2739, 2822, 2905, 2988, 3071, 3154,
3237, 3320, 3403, 3486, 3569, 3652, 3735, 3818, 3901, 3984, 4067, 4150,
4233, 4316, 4399, 4482, 4565, 4648, 4731, 4814, 4897, 4980, 5063, 5146,
5229, 5312, 5395, 5478, 5561, 5644, 5727, 5810, 5893, 5976, 6059, 6142,
6225, 6308, 6391, 6474, 6557, 6640, 6723, 6806, 6889, 6972, 7055, 7138,
7221, 7304, 7387, 7470, 7553, 7636, 7719, 7802, 7885, 7968, 8051, 8134,
8217, 8300, 8383, 8466, 8549, 8632, 8715, 8798, 8881, 8964, 9047, 9130,
9213, 9296, 9379, 9462, 9545, 9628, 9711, 9794, 9877, 9960, 10043, 10126,
10209, 10292, 10375, 10458, 10541, 10624, 10707, 10790, 10873, 10956,
11039, 11122, 11205, 11288, 11371, 11454, 11537, 11620, 11703, 11786,
11869, 11952, 12035, 12118, 12201, 12284, 12367, 12450, 12533, 12616,
12699, 12782, 12865, 12948, 13031, 13114, 13197, 13280, 13363, 13446,
13529, 13612, 13695, 13778, 13861, 13944, 14027, 14110, 14193, 14276,
14359, 14442, 14525, 14608, 14691, 14774, 14857, 14940, 15023, 15106,
15189, 15272, 15355, 15438, 15521, 15604, 15687, 15770, 15853, 15936,
16019, 16102, 16185, 16268, 16351, 16434, 16517, 16600, 16683, 16766,
16849, 16932, 17015, 17098, 17181, 17264, 17347, 17430, 17513, 17596,
17679, 17762, 17845, 17928, 18011, 18094, 18177, 18260, 18343, 18426,
18509, 18592, 18675, 18758, 18841, 18924, 19007, 19090, 19173, 19256,
19339, 19422, 19505, 19588, 19671, 19754, 19837, 19920, 20003, 20086,
20169, 20252, 20335, 20418, 20501, 20584, 20667, 20750, 20833, 20916,
20999, 21082, 21165
};
}
}
}

359
tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs

@ -1,359 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks
{
public partial class RgbToYCbCr
{
private const int InputColorCount = 64;
private const int InputByteCount = InputColorCount * 3;
private static readonly Vector3 VectorY = new Vector3(0.299F, 0.587F, 0.114F);
private static readonly Vector3 VectorCb = new Vector3(-0.168736F, 0.331264F, 0.5F);
private static readonly Vector3 VectorCr = new Vector3(0.5F, 0.418688F, 0.081312F);
private static class ScaledCoeffs
{
public static readonly int[] Y =
{
306, 601, 117, 0,
306, 601, 117, 0,
};
public static readonly int[] Cb =
{
-172, 339, 512, 0,
-172, 339, 512, 0,
};
public static readonly int[] Cr =
{
512, 429, 83, 0,
512, 429, 83, 0,
};
public static class SelectLeft
{
public static readonly int[] Y =
{
1, 1, 1, 0,
0, 0, 0, 0,
};
public static readonly int[] Cb =
{
1, -1, 1, 0,
0, 0, 0, 0,
};
public static readonly int[] Cr =
{
1, -1, -1, 0,
0, 0, 0, 0,
};
}
public static class SelectRight
{
public static readonly int[] Y =
{
0, 0, 0, 0,
1, 1, 1, 0,
};
public static readonly int[] Cb =
{
0, 0, 0, 0,
1, -1, 1, 0,
};
public static readonly int[] Cr =
{
0, 0, 0, 0,
1, -1, -1, 0,
};
}
}
private static class OnStackInputCache
{
public unsafe struct Byte
{
public fixed byte Data[InputByteCount * 3];
public static Byte Create(byte[] data)
{
Byte result = default;
for (int i = 0; i < data.Length; i++)
{
result.Data[i] = data[i];
}
return result;
}
}
}
public struct Result
{
internal Block8x8F Y;
internal Block8x8F Cb;
internal Block8x8F Cr;
}
// The operation is defined as "RGBA -> YCbCr Transform a stream of bytes into a stream of floats"
// We need to benchmark the whole operation, to get true results, not missing any side effects!
private byte[] inputSourceRGB;
private int[] inputSourceRGBAsInteger;
[GlobalSetup]
public void Setup()
{
// Console.WriteLine("Vector<int>.Count: " + Vector<int>.Count);
this.inputSourceRGB = new byte[InputByteCount];
for (int i = 0; i < this.inputSourceRGB.Length; i++)
{
this.inputSourceRGB[i] = (byte)(42 + i);
}
this.inputSourceRGBAsInteger = new int[InputByteCount + Vector<int>.Count]; // Filling this should be part of the measured operation
}
[Benchmark(Baseline = true, Description = "Floating Point Conversion")]
public unsafe void RgbaToYcbCrScalarFloat()
{
// Copy the input to the stack:
var input = OnStackInputCache.Byte.Create(this.inputSourceRGB);
// On-stack output:
Result result = default;
var yPtr = (float*)&result.Y;
var cbPtr = (float*)&result.Cb;
var crPtr = (float*)&result.Cr;
for (int i = 0; i < InputColorCount; i++)
{
int i3 = i * 3;
float r = input.Data[i3 + 0];
float g = input.Data[i3 + 1];
float b = input.Data[i3 + 2];
*yPtr++ = (0.299F * r) + (0.587F * g) + (0.114F * b);
*cbPtr++ = 128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
*crPtr++ = 128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
}
}
[Benchmark(Description = "Simd Floating Point Conversion")]
public unsafe void RgbaToYcbCrSimdFloat()
{
// Copy the input to the stack:
var input = OnStackInputCache.Byte.Create(this.inputSourceRGB);
// On-stack output:
Result result = default;
var yPtr = (float*)&result.Y;
var cbPtr = (float*)&result.Cb;
var crPtr = (float*)&result.Cr;
for (int i = 0; i < InputColorCount; i++)
{
int i3 = i * 3;
var vectorRgb = new Vector3(
input.Data[i3 + 0],
input.Data[i3 + 1],
input.Data[i3 + 2]);
Vector3 vectorY = VectorY * vectorRgb;
Vector3 vectorCb = VectorCb * vectorRgb;
Vector3 vectorCr = VectorCr * vectorRgb;
*yPtr++ = vectorY.X + vectorY.Y + vectorY.Z;
*cbPtr++ = 128 + (vectorCb.X - vectorCb.Y + vectorCb.Z);
*crPtr++ = 128 + (vectorCr.X - vectorCr.Y - vectorCr.Z);
}
}
[Benchmark(Description = "Scaled Integer Conversion + Vector<int>")]
public unsafe void RgbaToYcbCrScaledIntegerSimd()
{
// Copy the input to the stack:
// On-stack output:
Result result = default;
var yPtr = (float*)&result.Y;
var cbPtr = (float*)&result.Cb;
var crPtr = (float*)&result.Cr;
var yCoeffs = new Vector<int>(ScaledCoeffs.Y);
var cbCoeffs = new Vector<int>(ScaledCoeffs.Cb);
var crCoeffs = new Vector<int>(ScaledCoeffs.Cr);
for (int i = 0; i < this.inputSourceRGB.Length; i++)
{
this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i];
}
for (int i = 0; i < InputColorCount; i += 2)
{
var rgb = new Vector<int>(this.inputSourceRGBAsInteger, i * 3);
Vector<int> y = yCoeffs * rgb;
Vector<int> cb = cbCoeffs * rgb;
Vector<int> cr = crCoeffs * rgb;
*yPtr++ = (y[0] + y[1] + y[2]) >> 10;
*cbPtr++ = 128 + ((cb[0] - cb[1] + cb[2]) >> 10);
*crPtr++ = 128 + ((cr[0] - cr[1] - cr[2]) >> 10);
*yPtr++ = (y[4] + y[5] + y[6]) >> 10;
*cbPtr++ = 128 + ((cb[4] - cb[5] + cb[6]) >> 10);
*crPtr++ = 128 + ((cr[4] - cr[5] - cr[6]) >> 10);
}
}
/// <summary>
/// This should perform better. Coreclr emitted Vector.Dot() code lacks the vectorization even with IsHardwareAccelerated == true.
/// Kept this benchmark because maybe it will be improved in a future CLR release.
/// <see>
/// <cref>https://www.gamedev.net/topic/673396-c-systemnumericsvectors-slow/</cref>
/// </see>
/// </summary>
[Benchmark(Description = "Scaled Integer Conversion + Vector<int> + Dot Product")]
public unsafe void RgbaToYcbCrScaledIntegerSimdWithDotProduct()
{
// Copy the input to the stack:
// On-stack output:
Result result = default;
float* yPtr = (float*)&result.Y;
float* cbPtr = (float*)&result.Cb;
float* crPtr = (float*)&result.Cr;
var yCoeffs = new Vector<int>(ScaledCoeffs.Y);
var cbCoeffs = new Vector<int>(ScaledCoeffs.Cb);
var crCoeffs = new Vector<int>(ScaledCoeffs.Cr);
var leftY = new Vector<int>(ScaledCoeffs.SelectLeft.Y);
var leftCb = new Vector<int>(ScaledCoeffs.SelectLeft.Cb);
var leftCr = new Vector<int>(ScaledCoeffs.SelectLeft.Cr);
var rightY = new Vector<int>(ScaledCoeffs.SelectRight.Y);
var rightCb = new Vector<int>(ScaledCoeffs.SelectRight.Cb);
var rightCr = new Vector<int>(ScaledCoeffs.SelectRight.Cr);
for (int i = 0; i < this.inputSourceRGB.Length; i++)
{
this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i];
}
for (int i = 0; i < InputColorCount; i += 2)
{
var rgb = new Vector<int>(this.inputSourceRGBAsInteger, i * 3);
Vector<int> y = yCoeffs * rgb;
Vector<int> cb = cbCoeffs * rgb;
Vector<int> cr = crCoeffs * rgb;
VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, leftY, leftCb, leftCr);
VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, rightY, rightCb, rightCr);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void VectorizedConvertImpl(
ref float* yPtr,
ref float* cbPtr,
ref float* crPtr,
Vector<int> y,
Vector<int> cb,
Vector<int> cr,
Vector<int> yAgg,
Vector<int> cbAgg,
Vector<int> crAgg)
{
int ySum = Vector.Dot(y, yAgg);
int cbSum = Vector.Dot(cb, cbAgg);
int crSum = Vector.Dot(cr, crAgg);
*yPtr++ = ySum >> 10;
*cbPtr++ = 128 + (cbSum >> 10);
*crPtr++ = 128 + (crSum >> 10);
}
[Benchmark(Description = "Scaled Integer Conversion")]
public unsafe void RgbaToYcbCrScaledInteger()
{
// Copy the input to the stack:
var input = OnStackInputCache.Byte.Create(this.inputSourceRGB);
// On-stack output:
Result result = default;
float* yPtr = (float*)&result.Y;
float* cbPtr = (float*)&result.Cb;
float* crPtr = (float*)&result.Cr;
for (int i = 0; i < InputColorCount; i++)
{
int i3 = i * 3;
int r = input.Data[i3 + 0];
int g = input.Data[i3 + 1];
int b = input.Data[i3 + 2];
// Scale by 1024, add .5F and truncate value
int y0 = 306 * r; // (0.299F * 1024) + .5F
int y1 = 601 * g; // (0.587F * 1024) + .5F
int y2 = 117 * b; // (0.114F * 1024) + .5F
int cb0 = -172 * r; // (-0.168736F * 1024) + .5F
int cb1 = 339 * g; // (0.331264F * 1024) + .5F
int cb2 = 512 * b; // (0.5F * 1024) + .5F
int cr0 = 512 * r; // (0.5F * 1024) + .5F
int cr1 = 429 * g; // (0.418688F * 1024) + .5F
int cr2 = 83 * b; // (0.081312F * 1024) + .5F
*yPtr++ = (y0 + y1 + y2) >> 10;
*cbPtr++ = 128 + ((cb0 - cb1 + cb2) >> 10);
*crPtr++ = 128 + ((cr0 - cr1 - cr2) >> 10);
}
}
[Benchmark(Description = "Scaled Integer LUT Conversion")]
public unsafe void RgbaToYcbCrScaledIntegerLut()
{
// Copy the input to the stack:
var input = OnStackInputCache.Byte.Create(this.inputSourceRGB);
// On-stack output:
Result result = default;
float* yPtr = (float*)&result.Y;
float* cbPtr = (float*)&result.Cb;
float* crPtr = (float*)&result.Cr;
for (int i = 0; i < InputColorCount; i++)
{
int i3 = i * 3;
int r = input.Data[i3 + 0];
int g = input.Data[i3 + 1];
int b = input.Data[i3 + 2];
// TODO: Maybe concatenating all the arrays in LookupTables to a flat one can improve this!
*yPtr++ = (LookupTables.Y0[r] + LookupTables.Y1[g] + LookupTables.Y2[b]) >> 10;
*cbPtr++ = 128 + ((LookupTables.Cb0[r] - LookupTables.Cb1[g] + LookupTables.Cb2Cr0[b]) >> 10);
*crPtr++ = 128 + ((LookupTables.Cb2Cr0[r] - LookupTables.Cr1[g] - LookupTables.Cr2[b]) >> 10);
}
}
}
}

2
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -10,11 +10,13 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using PhotoSauce.MagicScaler;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
using SkiaSharp;

20
tests/ImageSharp.Tests.ProfilingSandbox/Program.cs

@ -66,12 +66,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
return null;
}
private static void RunJpegEncoderProfilingTests()
{
var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput());
benchmarks.EncodeJpeg_SingleMidSize();
}
private static void RunResizeProfilingTest()
{
var test = new ResizeProfilingBenchmarks(new ConsoleOutput());
@ -83,19 +77,5 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
var tests = new PixelOperationsTests.Rgba32_OperationsTests(new ConsoleOutput());
tests.Benchmark_ToVector4();
}
private static void RunDecodeJpegProfilingTests()
{
Console.WriteLine("RunDecodeJpegProfilingTests...");
var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput());
foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData)
{
string fileName = (string)data[0];
int executionCount = (int)data[1];
benchmarks.DecodeJpeg(fileName, executionCount);
}
Console.WriteLine("DONE.");
}
}
}

97
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs

@ -1,97 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// Uncomment this to turn unit tests into benchmarks:
// #define BENCHMARKING
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public partial class Block8x8FTests
{
public class CopyToBufferArea : JpegFixture
{
public CopyToBufferArea(ITestOutputHelper output)
: base(output)
{
}
private static void VerifyAllZeroOutsideSubArea(Buffer2D<float> buffer, int subX, int subY, int horizontalFactor = 1, int verticalFactor = 1)
{
for (int y = 0; y < 20; y++)
{
for (int x = 0; x < 20; x++)
{
if (x < subX || x >= subX + (8 * horizontalFactor) || y < subY || y >= subY + (8 * verticalFactor))
{
Assert.Equal(0, buffer[x, y]);
}
}
}
}
[Fact]
public void Copy1x1Scale()
{
Block8x8F block = CreateRandomFloatBlock(0, 100);
using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(20, 20, AllocationOptions.Clean))
{
Buffer2DRegion<float> region = buffer.GetRegion(5, 10, 8, 8);
block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride);
Assert.Equal(block[0, 0], buffer[5, 10]);
Assert.Equal(block[1, 0], buffer[6, 10]);
Assert.Equal(block[0, 1], buffer[5, 11]);
Assert.Equal(block[0, 7], buffer[5, 17]);
Assert.Equal(block[63], buffer[12, 17]);
VerifyAllZeroOutsideSubArea(buffer, 5, 10);
}
}
[Theory]
[InlineData(1, 1)]
[InlineData(1, 2)]
[InlineData(2, 1)]
[InlineData(2, 2)]
[InlineData(4, 2)]
[InlineData(4, 4)]
public void CopyTo(int horizontalFactor, int verticalFactor)
{
Block8x8F block = CreateRandomFloatBlock(0, 100);
var start = new Point(50, 50);
using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(100, 100, AllocationOptions.Clean))
{
Buffer2DRegion<float> region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor);
block.ScaledCopyTo(region, horizontalFactor, verticalFactor);
for (int y = 0; y < 8 * verticalFactor; y++)
{
for (int x = 0; x < 8 * horizontalFactor; x++)
{
int yy = y / verticalFactor;
int xx = x / horizontalFactor;
float expected = block[xx, yy];
float actual = region[x, y];
Assert.Equal(expected, actual);
}
}
VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor);
}
}
}
}
}

57
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -2,11 +2,9 @@
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tests.Colorspaces.Conversion;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -24,11 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private const int TestBufferLength = 40;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX;
#else
private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll;
#endif
private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision);
@ -46,14 +40,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact]
public void GetConverterThrowsExceptionOnInvalidColorSpace()
{
Assert.Throws<Exception>(() => JpegColorConverterBase.GetConverter(JpegColorSpace.Undefined, 8));
var invalidColorSpace = (JpegColorSpace)(-1);
Assert.Throws<Exception>(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8));
}
[Fact]
public void GetConverterThrowsExceptionOnInvalidPrecision()
{
// Valid precisions: 8 & 12 bit
Assert.Throws<Exception>(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 9));
int invalidPrecision = 9;
Assert.Throws<Exception>(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision));
}
[Theory]
@ -95,13 +91,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrVector(int seed)
{
var converter = new JpegColorConverterBase.FromYCbCrVector(8);
var converter = new JpegColorConverterBase.YCbCrVector(8);
if (!converter.IsAvailable)
{
@ -117,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromYCbCrVector(8),
new JpegColorConverterBase.YCbCrVector(8),
3,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -125,13 +121,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed);
this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykVector(int seed)
{
var converter = new JpegColorConverterBase.FromCmykVector(8);
var converter = new JpegColorConverterBase.CmykVector(8);
if (!converter.IsAvailable)
{
@ -147,7 +143,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromCmykVector(8),
new JpegColorConverterBase.CmykVector(8),
4,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -155,13 +151,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed);
this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleVector(int seed)
{
var converter = new JpegColorConverterBase.FromGrayScaleVector(8);
var converter = new JpegColorConverterBase.GrayScaleVector(8);
if (!converter.IsAvailable)
{
@ -177,7 +173,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromGrayScaleVector(8),
new JpegColorConverterBase.GrayScaleVector(8),
1,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -185,13 +181,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbVector(int seed)
{
var converter = new JpegColorConverterBase.FromRgbVector(8);
var converter = new JpegColorConverterBase.RgbVector(8);
if (!converter.IsAvailable)
{
@ -207,7 +203,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromRgbVector(8),
new JpegColorConverterBase.RgbVector(8),
3,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -215,13 +211,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed);
this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKVector(int seed)
{
var converter = new JpegColorConverterBase.FromYccKVector(8);
var converter = new JpegColorConverterBase.YccKVector(8);
if (!converter.IsAvailable)
{
@ -237,37 +233,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromYccKVector(8),
new JpegColorConverterBase.YccKVector(8),
4,
FeatureTestRunner.Deserialize<int>(arg));
}
#if SUPPORTS_RUNTIME_INTRINSICS
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed);
this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed);
this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed);
#endif
this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed);
private void TestConverter(
JpegColorConverterBase converter,
@ -357,7 +351,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
case JpegColorSpace.YCbCr:
ValidateYCbCr(original, result, i);
break;
case JpegColorSpace.Undefined:
default:
Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}.");
break;

1
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs

@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using Xunit;

29
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -153,15 +153,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)]
[InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegColorType.YCbCrRatio422)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)]
public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType)
[InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingColor.Luminance)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingColor.YCbCrRatio420)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingColor.YCbCrRatio444)]
[InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)]
public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
@ -171,12 +170,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)]
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegColorType.Cmyk)]
public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)]
public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);

148
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs

@ -2,8 +2,13 @@
// Licensed under the Six Labors Split License.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -13,6 +18,83 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Trait("Format", "Jpg")]
public partial class JpegEncoderTests
{
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new()
{
{ TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio },
{ TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch },
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
public static readonly TheoryData<string, int> QualityFiles =
new()
{
{ TestImages.Jpeg.Baseline.Calliphora, 80 },
{ TestImages.Jpeg.Progressive.Fb, 75 }
};
[Fact]
public void Encode_PreservesIptcProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
// act
using var memStream = new MemoryStream();
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
IptcProfile actual = output.Metadata.IptcProfile;
Assert.NotNull(actual);
IEnumerable<IptcValue> values = input.Metadata.IptcProfile.Values;
Assert.Equal(values, actual.Values);
}
[Fact]
public void Encode_PreservesExifProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.ExifProfile = new ExifProfile();
input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test");
// act
using var memStream = new MemoryStream();
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile actual = output.Metadata.ExifProfile;
Assert.NotNull(actual);
IReadOnlyList<IExifValue> values = input.Metadata.ExifProfile.Values;
Assert.Equal(values, actual.Values);
}
[Fact]
public void Encode_PreservesIccProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array);
// act
using var memStream = new MemoryStream();
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
IccProfile actual = output.Metadata.IccProfile;
Assert.NotNull(actual);
IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries);
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)]
public void Encode_WithValidExifProfile_DoesNotThrowException<TPixel>(TestImageProvider<TPixel> provider)
@ -29,5 +111,71 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Null(ex);
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, JpegEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}
[Theory]
[MemberData(nameof(QualityFiles))]
public void Encode_PreservesQuality(string imagePath, int quality)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, JpegEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
}
}
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)]
public void Encode_PreservesColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
using Image<TPixel> input = provider.GetImage(JpegDecoder);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, JpegEncoder);
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
}
}

473
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -1,15 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -26,189 +21,149 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static JpegDecoder JpegDecoder => new();
public static readonly TheoryData<string, int> QualityFiles =
new()
{
{ TestImages.Jpeg.Baseline.Calliphora, 80 },
{ TestImages.Jpeg.Progressive.Fb, 75 }
};
public static readonly TheoryData<JpegColorType, int> BitsPerPixel_Quality =
new()
{
{ JpegColorType.YCbCrRatio420, 40 },
{ JpegColorType.YCbCrRatio420, 60 },
{ JpegColorType.YCbCrRatio420, 100 },
{ JpegColorType.YCbCrRatio444, 40 },
{ JpegColorType.YCbCrRatio444, 60 },
{ JpegColorType.YCbCrRatio444, 100 },
{ JpegColorType.Rgb, 40 },
{ JpegColorType.Rgb, 60 },
{ JpegColorType.Rgb, 100 }
};
private static readonly TheoryData<int> TestQualities = new()
{
40,
80,
100,
};
public static readonly TheoryData<int> Grayscale_Quality =
new()
{
{ 40 },
{ 60 },
{ 100 }
};
public static readonly TheoryData<JpegEncodingColor, int, float> NonSubsampledEncodingSetups = new()
{
{ JpegEncodingColor.Rgb, 100, 0.0238f / 100 },
{ JpegEncodingColor.Rgb, 80, 1.3044f / 100 },
{ JpegEncodingColor.Rgb, 40, 2.9879f / 100 },
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new()
{
{ TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio },
{ TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch },
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
{ JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 },
{ JpegEncodingColor.YCbCrRatio444, 80, 1.4585f / 100 },
{ JpegEncodingColor.YCbCrRatio444, 40, 3.1413f / 100 },
};
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegColorType.Luminance)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegColorType.YCbCrRatio420)]
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegColorType.Rgb)]
public void Encode_PreservesColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
where TPixel : unmanaged, IPixel<TPixel>
public static readonly TheoryData<JpegEncodingColor, int, float> SubsampledEncodingSetups = new()
{
// arrange
using Image<TPixel> input = provider.GetImage(JpegDecoder);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, JpegEncoder);
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
{ JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 },
{ JpegEncodingColor.YCbCrRatio422, 80, 1.6043f / 100 },
{ JpegEncodingColor.YCbCrRatio422, 40, 3.1996f / 100 },
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg411, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg422, PixelTypes.Rgba32)]
public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{ JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 },
{ JpegEncodingColor.YCbCrRatio420, 80, 1.6692f / 100 },
{ JpegEncodingColor.YCbCrRatio420, 40, 3.2324f / 100 },
{ JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 },
{ JpegEncodingColor.YCbCrRatio411, 80, 1.7139f / 100 },
{ JpegEncodingColor.YCbCrRatio411, 40, 3.2634f / 100 },
{ JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 },
{ JpegEncodingColor.YCbCrRatio410, 80, 1.7495f / 100 },
{ JpegEncodingColor.YCbCrRatio410, 40, 3.2911f / 100 },
};
public static readonly TheoryData<JpegEncodingColor, int, float> CmykEncodingSetups = new()
{
// arrange
using Image<TPixel> input = provider.GetImage(JpegDecoder);
using var memoryStream = new MemoryStream();
{ JpegEncodingColor.Cmyk, 100, 0.0159f / 100 },
{ JpegEncodingColor.Cmyk, 80, 0.3922f / 100 },
{ JpegEncodingColor.Cmyk, 40, 0.6488f / 100 },
};
// act
input.Save(memoryStream, new JpegEncoder()
{
Quality = 75
});
public static readonly TheoryData<JpegEncodingColor, int, float> YcckEncodingSetups = new()
{
{ JpegEncodingColor.Ycck, 100, 0.0356f / 100 },
{ JpegEncodingColor.Ycck, 80, 0.1245f / 100 },
{ JpegEncodingColor.Ycck, 40, 0.2663f / 100 },
};
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType);
}
public static readonly TheoryData<JpegEncodingColor, int, float> LuminanceEncodingSetups = new()
{
{ JpegEncodingColor.Luminance, 100, 0.0175f / 100 },
{ JpegEncodingColor.Luminance, 80, 0.6730f / 100 },
{ JpegEncodingColor.Luminance, 40, 0.9941f / 100 },
};
[Theory]
[InlineData(JpegColorType.Cmyk)]
[InlineData(JpegColorType.YCbCrRatio410)]
[InlineData(JpegColorType.YCbCrRatio411)]
[InlineData(JpegColorType.YCbCrRatio422)]
public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegColorType colorType)
{
// arrange
var jpegEncoder = new JpegEncoder() { ColorType = colorType };
using var input = new Image<Rgb24>(10, 10);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, jpegEncoder);
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType);
}
[WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)]
[WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)]
public void EncodeBaseline_Interleaved<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, tolerance);
[Theory]
[MemberData(nameof(QualityFiles))]
public void Encode_PreservesQuality(string imagePath, int quality)
[WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)]
[WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)]
public void EncodeBaseline_NonInterleavedMode<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel>
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
using Image<TPixel> image = provider.GetImage();
var encoder = new JpegEncoder
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, JpegEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
}
}
}
Quality = quality,
ColorType = colorType,
Interleaved = false,
};
string info = $"{colorType}-Q{quality}";
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
public void EncodeBaseline_CalliphoraPartial<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
ImageComparer comparer = new TolerantImageComparer(tolerance);
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg");
}
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[Theory]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f));
[WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
[WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)]
public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f));
[Theory]
[WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgba32, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)]
public void EncodeBaseline_Grayscale<TPixel>(TestImageProvider<TPixel> provider, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, JpegColorType.Luminance, quality);
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality);
[Theory]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)]
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)]
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[Theory]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
[WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)]
public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f));
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegColorType.YCbCrRatio420)]
[WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegColorType.YCbCrRatio420)]
public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType)
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
[WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
[WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
[WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444
ImageComparer comparer = colorType == JpegEncodingColor.YCbCrRatio444
? ImageComparer.TolerantPercentage(0.1f)
: ImageComparer.TolerantPercentage(5f);
@ -216,10 +171,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestJpegEncoderCore(provider, colorType, 100, comparer);
}
[Theory]
[InlineData(JpegEncodingColor.YCbCrRatio420)]
[InlineData(JpegEncodingColor.YCbCrRatio444)]
public async Task Encode_IsCancellable(JpegEncodingColor colorType)
{
var cts = new CancellationTokenSource();
using var pausedStream = new PausedStream(new MemoryStream());
pausedStream.OnWaiting(s =>
{
// after some writing
if (s.Position >= 500)
{
cts.Cancel();
pausedStream.Release();
}
else
{
// allows this/next wait to unblock
pausedStream.Next();
}
});
using var image = new Image<Rgba32>(5000, 5000);
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
var encoder = new JpegEncoder() { ColorType = colorType };
await image.SaveAsync(pausedStream, encoder, cts.Token);
});
}
/// <summary>
/// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
/// </summary>
private static ImageComparer GetComparer(int quality, JpegColorType? colorType)
private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType)
{
float tolerance = 0.015f; // ~1.5%
@ -227,10 +212,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
tolerance *= 4.5f;
}
else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420)
{
tolerance *= 2.0f;
if (colorType == JpegColorType.YCbCrRatio420)
if (colorType == JpegEncodingColor.YCbCrRatio420)
{
tolerance *= 2.0f;
}
@ -239,18 +224,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return ImageComparer.Tolerant(tolerance);
}
private static void TestJpegEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
JpegColorType colorType = JpegColorType.YCbCrRatio420,
int quality = 100,
ImageComparer comparer = null)
private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel>
=> TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType));
private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
where TPixel : unmanaged, IPixel<TPixel>
=> TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance));
private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, ImageComparer comparer)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
// There is no alpha in Jpeg!
image.Mutate(c => c.MakeOpaque());
var encoder = new JpegEncoder
{
Quality = quality,
@ -258,171 +244,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
};
string info = $"{colorType}-Q{quality}";
comparer ??= GetComparer(quality, colorType);
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png");
}
[Fact]
public void Quality_0_And_1_Are_Identical()
{
var options = new JpegEncoder
{
Quality = 0
};
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
using (var memStream0 = new MemoryStream())
using (var memStream1 = new MemoryStream())
{
input.SaveAsJpeg(memStream0, options);
options.Quality = 1;
input.SaveAsJpeg(memStream1, options);
Assert.Equal(memStream0.ToArray(), memStream1.ToArray());
}
}
[Fact]
public void Quality_0_And_100_Are_Not_Identical()
{
var options = new JpegEncoder
{
Quality = 0
};
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
using (var memStream0 = new MemoryStream())
using (var memStream1 = new MemoryStream())
{
input.SaveAsJpeg(memStream0, options);
options.Quality = 100;
input.SaveAsJpeg(memStream1, options);
Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray());
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, JpegEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}
[Fact]
public void Encode_PreservesIptcProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
// act
using var memStream = new MemoryStream();
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
IptcProfile actual = output.Metadata.IptcProfile;
Assert.NotNull(actual);
IEnumerable<IptcValue> values = input.Metadata.IptcProfile.Values;
Assert.Equal(values, actual.Values);
}
[Fact]
public void Encode_PreservesExifProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.ExifProfile = new ExifProfile();
input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test");
// act
using var memStream = new MemoryStream();
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile actual = output.Metadata.ExifProfile;
Assert.NotNull(actual);
IReadOnlyList<IExifValue> values = input.Metadata.ExifProfile.Values;
Assert.Equal(values, actual.Values);
}
[Fact]
public void Encode_PreservesIccProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array);
// act
using var memStream = new MemoryStream();
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
IccProfile actual = output.Metadata.IccProfile;
Assert.NotNull(actual);
IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries);
}
[Theory]
[InlineData(JpegColorType.YCbCrRatio420)]
[InlineData(JpegColorType.YCbCrRatio444)]
public async Task Encode_IsCancellable(JpegColorType colorType)
{
var cts = new CancellationTokenSource();
using var pausedStream = new PausedStream(new MemoryStream());
pausedStream.OnWaiting(s =>
{
// after some writing
if (s.Position >= 500)
{
cts.Cancel();
pausedStream.Release();
}
else
{
// allows this/next wait to unblock
pausedStream.Next();
}
});
using var image = new Image<Rgba32>(5000, 5000);
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
var encoder = new JpegEncoder() { ColorType = colorType };
await image.SaveAsync(pausedStream, encoder, cts.Token);
});
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save