Browse Source

Refactoring, fixes, tests

pull/2120/head
Dmitry Pentin 4 years ago
parent
commit
97f200d475
  1. 8
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  2. 20
      src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs
  4. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs
  5. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs
  6. 8
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs
  7. 16
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs
  8. 16
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs
  9. 8
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs
  10. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs
  11. 10
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs
  12. 8
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs
  13. 12
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs
  14. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs
  15. 16
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs
  16. 12
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs
  17. 4
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs
  18. 14
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs
  19. 32
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  20. 4
      src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs
  21. 26
      src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs
  22. 30
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs
  23. 42
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs
  24. 21
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs
  25. 20
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs
  26. 71
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  27. 27
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
  28. 87
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs
  29. 5
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  30. 18
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  31. 277
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  32. 311
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  33. 38
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  34. 11
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs
  35. 19
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  36. 6
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
  37. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs
  38. 6
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs
  39. 6
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs
  40. 6
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs
  41. 46
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
  42. 49
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  43. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  44. 76
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

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

@ -968,10 +968,10 @@ namespace SixLabors.ImageSharp
} }
internal static void UnpackToRgbPlanesAvx2Reduce( internal static void UnpackToRgbPlanesAvx2Reduce(
ref ReadOnlySpan<float> redChannel, ref Span<float> redChannel,
ref ReadOnlySpan<float> greenChannel, ref Span<float> greenChannel,
ref ReadOnlySpan<float> blueChannel, ref Span<float> blueChannel,
ref Span<Rgb24> source) ref ReadOnlySpan<Rgb24> source)
{ {
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(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> destRRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(redChannel));

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

@ -67,10 +67,10 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal static void UnpackToRgbPlanes( internal static void UnpackToRgbPlanes(
ReadOnlySpan<float> redChannel, Span<float> redChannel,
ReadOnlySpan<float> greenChannel, Span<float> greenChannel,
ReadOnlySpan<float> blueChannel, Span<float> blueChannel,
Span<Rgb24> source) ReadOnlySpan<Rgb24> source)
{ {
DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); 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(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!");
@ -221,11 +221,15 @@ namespace SixLabors.ImageSharp
} }
private static void UnpackToRgbPlanesScalar( private static void UnpackToRgbPlanesScalar(
ReadOnlySpan<float> redChannel, Span<float> redChannel,
ReadOnlySpan<float> greenChannel, Span<float> greenChannel,
ReadOnlySpan<float> blueChannel, Span<float> blueChannel,
Span<Rgb24> source) 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 r = ref MemoryMarshal.GetReference(redChannel);
ref float g = ref MemoryMarshal.GetReference(greenChannel); ref float g = ref MemoryMarshal.GetReference(greenChannel);
ref float b = ref MemoryMarshal.GetReference(blueChannel); ref float b = ref MemoryMarshal.GetReference(blueChannel);

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

@ -1,10 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable UseObjectOrCollectionInitializer // ReSharper disable UseObjectOrCollectionInitializer
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromCmykAvx : JpegColorConverterAvx internal sealed class CmykAvx : JpegColorConverterAvx
{ {
public FromCmykAvx(int precision) public CmykAvx(int precision)
: base(JpegColorSpace.Cmyk, precision) : base(JpegColorSpace.Cmyk, precision)
{ {
} }

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs

@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromCmykScalar : JpegColorConverterScalar internal sealed class CmykScalar : JpegColorConverterScalar
{ {
public FromCmykScalar(int precision) public CmykScalar(int precision)
: base(JpegColorSpace.Cmyk, precision) : base(JpegColorSpace.Cmyk, precision)
{ {
} }

8
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs

@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromCmykVector : JpegColorConverterVector internal sealed class CmykVector : JpegColorConverterVector
{ {
public FromCmykVector(int precision) public CmykVector(int precision)
: base(JpegColorSpace.Cmyk, precision) : base(JpegColorSpace.Cmyk, precision)
{ {
} }
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceToRgb(in ComponentValues values) protected override void ConvertCoreInplaceToRgb(in ComponentValues values)
=> FromCmykScalar.ConvertToRgbInplace(values, this.MaximumValue); => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue);
protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
{ {
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> FromCmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); => CmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b);
} }
} }
} }

16
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs

@ -13,15 +13,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromGrayscaleAvx : JpegColorConverterAvx internal sealed class GrayscaleAvx : JpegColorConverterAvx
{ {
public FromGrayscaleAvx(int precision) public GrayscaleAvx(int precision)
: base(JpegColorSpace.Grayscale, precision) : base(JpegColorSpace.Grayscale, precision)
{ {
} }
public override void ConvertToRgbInplace(in ComponentValues values) 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);
}
} }
public override void ConvertFromRgbInplace(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) public override void ConvertFromRgbInplace(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)

16
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs

@ -2,14 +2,16 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromGrayscaleScalar : JpegColorConverterScalar internal sealed class GrayscaleScalar : JpegColorConverterScalar
{ {
public FromGrayscaleScalar(int precision) public GrayscaleScalar(int precision)
: base(JpegColorSpace.Grayscale, precision) : base(JpegColorSpace.Grayscale, precision)
{ {
} }
@ -22,6 +24,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
internal static void ConvertCoreInplaceToRgb(Span<float> values, float maxValue) internal static void ConvertCoreInplaceToRgb(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) internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
@ -35,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
float b = bLane[i]; float b = bLane[i];
// luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b)
c0[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); float luma = (0.299f * r) + (0.587f * g) + (0.114f * b);
c0[i] = luma;
} }
} }
} }

8
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs

@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromGrayScaleVector : JpegColorConverterVector internal sealed class GrayScaleVector : JpegColorConverterVector
{ {
public FromGrayScaleVector(int precision) public GrayScaleVector(int precision)
: base(JpegColorSpace.Grayscale, precision) : base(JpegColorSpace.Grayscale, precision)
{ {
} }
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceToRgb(in ComponentValues values) protected override void ConvertCoreInplaceToRgb(in ComponentValues values)
=> FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); => GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue);
protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{ {
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); => GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b);
} }
} }
} }

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

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase 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) : base(JpegColorSpace.RGB, precision)
{ {
} }

10
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs

@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromRgbScalar : JpegColorConverterScalar internal sealed class RgbScalar : JpegColorConverterScalar
{ {
public FromRgbScalar(int precision) public RgbScalar(int precision)
: base(JpegColorSpace.RGB, precision) : base(JpegColorSpace.RGB, precision)
{ {
} }
@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue) internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue)
{ {
FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue); GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue);
FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue); GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue);
FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue);
} }
internal static void ConvertCoreInplaceFromRgb(ComponentValues values, Span<float> r, Span<float> g, Span<float> b) internal static void ConvertCoreInplaceFromRgb(ComponentValues values, Span<float> r, Span<float> g, Span<float> b)

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

@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase 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) : base(JpegColorSpace.RGB, precision)
{ {
} }
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceToRgb(in ComponentValues values) protected override void ConvertCoreInplaceToRgb(in ComponentValues values)
=> FromRgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); => RgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue);
protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
{ {
@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> FromRgbScalar.ConvertCoreInplaceFromRgb(values, r, g, b); => RgbScalar.ConvertCoreInplaceFromRgb(values, r, g, b);
} }
} }
} }

12
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs

@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromYCbCrAvx : JpegColorConverterAvx internal sealed class YCbCrAvx : JpegColorConverterAvx
{ {
public FromYCbCrAvx(int precision) public YCbCrAvx(int precision)
: base(JpegColorSpace.YCbCr, precision) : base(JpegColorSpace.YCbCr, precision)
{ {
} }
@ -33,10 +33,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
// Used for the color conversion // Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue); var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); var rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step: // Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count; nint n = values.Component0.Length / Vector256<float>.Count;

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs

@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromYCbCrScalar : JpegColorConverterScalar internal sealed class YCbCrScalar : JpegColorConverterScalar
{ {
// TODO: comments, derived from ITU-T Rec. T.871 // TODO: comments, derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f; internal const float RCrMult = 1.402f;
@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f; internal const float BCbMult = 1.772f;
public FromYCbCrScalar(int precision) public YCbCrScalar(int precision)
: base(JpegColorSpace.YCbCr, precision) : base(JpegColorSpace.YCbCr, precision)
{ {
} }

16
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs

@ -11,9 +11,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromYCbCrVector : JpegColorConverterVector internal sealed class YCbCrVector : JpegColorConverterVector
{ {
public FromYCbCrVector(int precision) public YCbCrVector(int precision)
: base(JpegColorSpace.YCbCr, precision) : base(JpegColorSpace.YCbCr, precision)
{ {
} }
@ -30,10 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var chromaOffset = new Vector<float>(-this.HalfValue); var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult); var rCrMult = new Vector<float>(YCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult); var gCbMult = new Vector<float>(-YCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult); var gCrMult = new Vector<float>(-YCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult); var bCbMult = new Vector<float>(YCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count; nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++) for (nint i = 0; i < n; i++)
@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceToRgb(in ComponentValues values) protected override void ConvertCoreInplaceToRgb(in ComponentValues values)
=> FromYCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); => YCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue);
protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{ {
@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> FromYCbCrScalar.ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); => YCbCrScalar.ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b);
} }
} }
} }

12
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs

@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromYccKAvx : JpegColorConverterAvx internal sealed class YccKAvx : JpegColorConverterAvx
{ {
public FromYccKAvx(int precision) public YccKAvx(int precision)
: base(JpegColorSpace.Ycck, precision) : base(JpegColorSpace.Ycck, precision)
{ {
} }
@ -35,10 +35,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var chromaOffset = Vector256.Create(-this.HalfValue); var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue); var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); var rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step: // Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count; nint n = values.Component0.Length / Vector256<float>.Count;

4
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs

@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromYccKScalar : JpegColorConverterScalar internal sealed class YccKScalar : JpegColorConverterScalar
{ {
public FromYccKScalar(int precision) public YccKScalar(int precision)
: base(JpegColorSpace.Ycck, precision) : base(JpegColorSpace.Ycck, precision)
{ {
} }

14
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs → src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs

@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal abstract partial class JpegColorConverterBase internal abstract partial class JpegColorConverterBase
{ {
internal sealed class FromYccKVector : JpegColorConverterVector internal sealed class YccKVector : JpegColorConverterVector
{ {
public FromYccKVector(int precision) public YccKVector(int precision)
: base(JpegColorSpace.Ycck, precision) : base(JpegColorSpace.Ycck, precision)
{ {
} }
@ -31,10 +31,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var chromaOffset = new Vector<float>(-this.HalfValue); var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue)); var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
var max = new Vector<float>(this.MaximumValue); var max = new Vector<float>(this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult); var rCrMult = new Vector<float>(YCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult); var gCbMult = new Vector<float>(-YCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult); var gCrMult = new Vector<float>(-YCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult); var bCbMult = new Vector<float>(YCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count; nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++) for (nint i = 0; i < n; i++)
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => protected override void ConvertCoreInplaceToRgb(in ComponentValues values) =>
FromYccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue);
protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span<float> r, Span<float> g, Span<float> b)
=> throw new System.NotImplementedException(); => throw new System.NotImplementedException();

32
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -110,9 +110,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
private static IEnumerable<JpegColorConverterBase> GetYCbCrConverters(int precision) private static IEnumerable<JpegColorConverterBase> GetYCbCrConverters(int precision)
{ {
yield return new FromYCbCrAvx(precision); yield return new YCbCrAvx(precision);
yield return new FromYCbCrVector(precision); yield return new YCbCrVector(precision);
yield return new FromYCbCrScalar(precision); yield return new YCbCrScalar(precision);
} }
/// <summary> /// <summary>
@ -120,9 +120,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
private static IEnumerable<JpegColorConverterBase> GetYccKConverters(int precision) private static IEnumerable<JpegColorConverterBase> GetYccKConverters(int precision)
{ {
yield return new FromYccKAvx(precision); yield return new YccKAvx(precision);
yield return new FromYccKVector(precision); yield return new YccKVector(precision);
yield return new FromYccKScalar(precision); yield return new YccKScalar(precision);
} }
/// <summary> /// <summary>
@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
private static IEnumerable<JpegColorConverterBase> GetCmykConverters(int precision) private static IEnumerable<JpegColorConverterBase> GetCmykConverters(int precision)
{ {
yield return new FromCmykAvx(precision); yield return new CmykAvx(precision);
yield return new FromCmykVector(precision); yield return new CmykVector(precision);
yield return new FromCmykScalar(precision); yield return new CmykScalar(precision);
} }
/// <summary> /// <summary>
@ -140,9 +140,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
private static IEnumerable<JpegColorConverterBase> GetGrayScaleConverters(int precision) private static IEnumerable<JpegColorConverterBase> GetGrayScaleConverters(int precision)
{ {
yield return new FromGrayscaleAvx(precision); yield return new GrayscaleAvx(precision);
yield return new FromGrayScaleVector(precision); yield return new GrayScaleVector(precision);
yield return new FromGrayscaleScalar(precision); yield return new GrayscaleScalar(precision);
} }
/// <summary> /// <summary>
@ -150,9 +150,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
private static IEnumerable<JpegColorConverterBase> GetRgbConverters(int precision) private static IEnumerable<JpegColorConverterBase> GetRgbConverters(int precision)
{ {
yield return new FromRgbAvx(precision); yield return new RgbAvx(precision);
yield return new FromRgbVector(precision); yield return new RgbVector(precision);
yield return new FromRgbScalar(precision); yield return new RgbScalar(precision);
} }
/// <summary> /// <summary>
@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
/// <param name="processors">List of component color processors.</param> /// <param name="processors">List of component color processors.</param>
/// <param name="row">Row to convert</param> /// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<Encoder.JpegComponentPostProcessor> processors, int row) public ComponentValues(IReadOnlyList<Encoder.ComponentProcessor> processors, int row)
{ {
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));

4
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs

@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Represents a single frame component. /// Represents a single frame component.
/// </summary> /// </summary>
internal class JpegComponent : IDisposable internal class Component : IDisposable
{ {
private readonly MemoryAllocator memoryAllocator; private readonly MemoryAllocator memoryAllocator;
public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex)
{ {
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;

26
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs

@ -11,26 +11,27 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
internal class JpegComponentPostProcessor : IDisposable internal class ComponentProcessor : IDisposable
{ {
private readonly Size blockAreaSize; private readonly Size blockAreaSize;
private readonly JpegComponent component; private readonly Component component;
private Block8x8F quantTable; private Block8x8F quantTable;
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent component, Size postProcessorBufferSize, Block8x8F quantTable) public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable)
{ {
this.component = component; this.component = component;
this.quantTable = quantTable; this.quantTable = quantTable;
FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable);
this.component = component; this.component = component;
this.blockAreaSize = component.SubSamplingDivisors * 8; 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>( this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width, postProcessorBufferSize.Width,
postProcessorBufferSize.Height, postProcessorBufferSize.Height,
8 * component.SubSamplingDivisors.Height, 8,
AllocationOptions.Clean); AllocationOptions.Clean);
} }
@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// but 12-bit jpegs are not supported currently // but 12-bit jpegs are not supported currently
float normalizationValue = -128f; float normalizationValue = -128f;
int destAreaStride = this.ColorBuffer.Width * this.component.SubSamplingDivisors.Height; int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = spectralStep * this.component.SamplingFactors.Height; int yBlockStart = spectralStep * this.component.SamplingFactors.Height;
@ -97,22 +98,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
Size factors = this.component.SubSamplingDivisors; Size factors = this.component.SubSamplingDivisors;
int packedWidth = this.ColorBuffer.Width / factors.Width;
float averageMultiplier = 1f / (factors.Width * factors.Height); float averageMultiplier = 1f / (factors.Width * factors.Height);
for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height)
{ {
Span<float> targetBufferRow = this.ColorBuffer.DangerousGetRowSpan(i); Span<float> sourceRow = this.ColorBuffer.DangerousGetRowSpan(i);
// vertical sum // vertical sum
for (int j = 1; j < factors.Height; j++) for (int j = 1; j < factors.Height; j++)
{ {
SumVertical(targetBufferRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j));
} }
// horizontal sum // horizontal sum
SumHorizontal(targetBufferRow, factors.Width); SumHorizontal(sourceRow, factors.Width);
// calculate average // calculate average
MultiplyToAverage(targetBufferRow, averageMultiplier); 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) static void SumVertical(Span<float> target, Span<float> source)

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

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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; }
}
}

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

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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; }
}
}

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

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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 Apache License, Version 2.0.
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; }
}
}

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

@ -135,6 +135,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table);
} }
/// <summary>
/// Encodes scan in baseline interleaved mode.
/// </summary>
/// <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) public void EncodeScanBaselineInterleaved<TPixel>(JpegEncodingColor color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -142,20 +149,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
case JpegEncodingColor.YCbCrRatio444: case JpegEncodingColor.YCbCrRatio444:
case JpegEncodingColor.Rgb: case JpegEncodingColor.Rgb:
this.EncodeScanBaselineInterleaved444(frame, converter, cancellationToken); this.EncodeThreeComponentScanBaselineInterleaved444(frame, converter, cancellationToken);
break;
case JpegEncodingColor.YCbCrRatio420:
this.EncodeScanBaselineInterleaved420(frame, converter, cancellationToken);
break; break;
default: default:
this.EncodeScanBaselineInterleavedArbitrarySampling(frame, converter, cancellationToken); this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken);
break; break;
} }
this.FlushRemainingBytes();
} }
public void EncodeScanBaselineSingleComponent<TPixel>(JpegComponent component, SpectralConverter<TPixel> converter, CancellationToken cancellationToken) /// <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> where TPixel : unmanaged, IPixel<TPixel>
{ {
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
@ -189,9 +197,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
} }
} }
this.FlushRemainingBytes();
} }
public void EncodeScanBaseline(JpegComponent component, CancellationToken cancellationToken) /// <summary>
/// Encodes scan with a single component in baseline non-interleaved mode.
/// </summary>
/// <param name="component">Component with grayscale data.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeScanBaseline(Component component, CancellationToken cancellationToken)
{ {
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
@ -225,7 +240,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.FlushRemainingBytes(); this.FlushRemainingBytes();
} }
private void EncodeScanBaselineInterleavedArbitrarySampling<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken) /// <summary>
/// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors.
/// </summary>
/// <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> where TPixel : unmanaged, IPixel<TPixel>
{ {
int mcu = 0; int mcu = 0;
@ -246,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int mcuCol = mcu % mcusPerLine; int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < frame.Components.Length; k++) for (int k = 0; k < frame.Components.Length; k++)
{ {
JpegComponent component = frame.Components[k]; Component component = frame.Components[k];
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
@ -282,17 +303,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
} }
} }
this.FlushRemainingBytes();
} }
private void EncodeScanBaselineInterleaved444<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken) /// <summary>
/// Encodes scan in baseline interleaved mode with exactly 3 components with 4:4:4 sampling.
/// </summary>
/// <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 EncodeThreeComponentScanBaselineInterleaved444<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int mcusPerColumn = frame.McusPerColumn; int mcusPerColumn = frame.McusPerColumn;
int mcusPerLine = frame.McusPerLine; int mcusPerLine = frame.McusPerLine;
JpegComponent c2 = frame.Components[2]; Component c2 = frame.Components[2];
JpegComponent c1 = frame.Components[1]; Component c1 = frame.Components[1];
JpegComponent c0 = frame.Components[0]; Component c0 = frame.Components[0];
ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId];
ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId];
@ -339,16 +368,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
} }
} }
}
private void EncodeScanBaselineInterleaved420<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken) this.FlushRemainingBytes();
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
} }
private void WriteBlock( private void WriteBlock(
JpegComponent component, Component component,
ref Block8x8 block, ref Block8x8 block,
ref HuffmanLut dcTable, ref HuffmanLut dcTable,
ref HuffmanLut acTable) ref HuffmanLut acTable)
@ -611,7 +636,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Flushes spectral data bytes after encoding all channel blocks /// Flushes spectral data bytes after encoding all channel blocks
/// in a single jpeg macroblock using <see cref="WriteBlock(JpegComponent, ref Block8x8, ref HuffmanLut, ref HuffmanLut)"/>. /// in a single jpeg macroblock using <see cref="WriteBlock(Component, ref Block8x8, ref HuffmanLut, ref HuffmanLut)"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true /// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
@ -641,7 +666,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount;
this.FlushToStream(lastByteIndex); this.FlushToStream(lastByteIndex);
// Clean huffman register // Clear huffman register
// This is needed for for images with multiples scans // This is needed for for images with multiples scans
this.bitCount = 0; this.bitCount = 0;
this.accumulatedBits = 0; this.accumulatedBits = 0;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@ -11,19 +12,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary> /// </summary>
internal sealed class JpegFrame : IDisposable internal sealed class JpegFrame : IDisposable
{ {
public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, JpegColorSpace colorSpace) public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved)
{ {
this.ColorSpace = colorSpace; this.ColorSpace = frameConfig.ColorType;
this.Interleaved = interleaved;
this.PixelWidth = image.Width; this.PixelWidth = image.Width;
this.PixelHeight = image.Height; this.PixelHeight = image.Height;
MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator;
JpegComponentConfig[] componentConfigs = frameConfig.Components; JpegComponentConfig[] componentConfigs = frameConfig.Components;
this.Components = new JpegComponent[componentConfigs.Length]; this.Components = new Component[componentConfigs.Length];
for (int i = 0; i < this.Components.Length; i++) for (int i = 0; i < this.Components.Length; i++)
{ {
JpegComponentConfig componentConfig = componentConfigs[i]; JpegComponentConfig componentConfig = componentConfigs[i];
this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex)
{ {
DcTableId = componentConfig.DcTableSelector, DcTableId = componentConfig.DcTableSelector,
AcTableId = componentConfig.AcTableSelector, AcTableId = componentConfig.AcTableSelector,
@ -39,18 +44,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
for (int i = 0; i < this.Components.Length; i++) for (int i = 0; i < this.Components.Length; i++)
{ {
JpegComponent component = this.Components[i]; Component component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV); component.Init(this, maxSubFactorH, maxSubFactorV);
} }
} }
public JpegColorSpace ColorSpace { get; } public JpegColorSpace ColorSpace { get; }
public int PixelHeight { get; private set; } public bool Interleaved { get; }
public int PixelHeight { get; }
public int PixelWidth { get; private set; } public int PixelWidth { get; }
public JpegComponent[] Components { get; } public Component[] Components { get; }
public int McusPerLine { get; } public int McusPerLine { get; }
@ -62,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
for (int i = 0; i < this.Components.Length; i++) for (int i = 0; i < this.Components.Length; i++)
{ {
this.Components[i]?.Dispose(); this.Components[i].Dispose();
} }
} }
@ -70,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
for (int i = 0; i < this.Components.Length; i++) for (int i = 0; i < this.Components.Length; i++)
{ {
JpegComponent component = this.Components[i]; Component component = this.Components[i];
component.AllocateSpectral(fullScan); component.AllocateSpectral(fullScan);
} }
} }

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

@ -4,40 +4,37 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
internal class SpectralConverter<TPixel> : SpectralConverter internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly Configuration configuration; private readonly ComponentProcessor[] componentProcessors;
private JpegComponentPostProcessor[] componentProcessors; private readonly int pixelRowsPerStep;
private int pixelRowsPerStep;
private int pixelRowCounter; private int pixelRowCounter;
private Buffer2D<TPixel> pixelBuffer; private readonly Buffer2D<TPixel> pixelBuffer;
private IMemoryOwner<float> redLane; private readonly IMemoryOwner<float> redLane;
private IMemoryOwner<float> greenLane; private readonly IMemoryOwner<float> greenLane;
private IMemoryOwner<float> blueLane; private readonly IMemoryOwner<float> blueLane;
private int alignedPixelWidth; private readonly int alignedPixelWidth;
private JpegColorConverterBase colorConverter; private readonly JpegColorConverterBase colorConverter;
public SpectralConverter(JpegFrame frame, Image<TPixel> image, Block8x8F[] dequantTables, Configuration configuration) public SpectralConverter(JpegFrame frame, Image<TPixel> image, Block8x8F[] dequantTables)
{ {
this.configuration = configuration; MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator;
MemoryAllocator allocator = this.configuration.MemoryAllocator;
// iteration data // iteration data
int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);
@ -47,23 +44,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight;
// pixel buffer of the image // pixel buffer of the image
// currently codec only supports encoding single frame jpegs
this.pixelBuffer = image.GetRootFramePixelBuffer(); this.pixelBuffer = image.GetRootFramePixelBuffer();
// component processors from spectral to Rgba32 // component processors from spectral to Rgb24
const int blockPixelWidth = 8; const int blockPixelWidth = 8;
this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; this.alignedPixelWidth = majorBlockWidth * blockPixelWidth;
var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep);
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; this.componentProcessors = new ComponentProcessor[frame.Components.Length];
for (int i = 0; i < this.componentProcessors.Length; i++) for (int i = 0; i < this.componentProcessors.Length; i++)
{ {
JpegComponent component = frame.Components[i]; Component component = frame.Components[i];
this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, component, postProcessorBufferSize, dequantTables[component.QuantizationTableIndex]); this.componentProcessors[i] = new ComponentProcessor(
allocator,
component,
postProcessorBufferSize,
dequantTables[component.QuantizationTableIndex]);
} }
this.redLane = allocator.Allocate<float>(this.alignedPixelWidth); this.redLane = allocator.Allocate<float>(this.alignedPixelWidth, AllocationOptions.Clean);
this.greenLane = allocator.Allocate<float>(this.alignedPixelWidth); this.greenLane = allocator.Allocate<float>(this.alignedPixelWidth, AllocationOptions.Clean);
this.blueLane = allocator.Allocate<float>(this.alignedPixelWidth); this.blueLane = allocator.Allocate<float>(this.alignedPixelWidth, AllocationOptions.Clean);
// color converter from Rgb24 to YCbCr // color converter from Rgb24 to YCbCr
this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8);
@ -71,10 +71,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void ConvertStrideBaseline() 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' // Convert next pixel stride using single spectral `stride'
// Note that zero passing eliminates the need of virtual call // Note that zero passing eliminates the need of virtual call
// from JpegComponentPostProcessor // from JpegComponentPostProcessor
this.ConvertStride(spectralStep: 0); this.ConvertStride(spectralStep: 0);
#pragma warning restore IDE0022
} }
public void ConvertFull() public void ConvertFull()
@ -88,34 +93,48 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private void ConvertStride(int spectralStep) private void ConvertStride(int spectralStep)
{ {
// 1. Unpack from TPixel to r/g/b planes int start = this.pixelRowCounter;
// 2. Byte r/g/b planes to normalized float r/g/b planes int end = start + this.pixelRowsPerStep;
// 3. Convert from r/g/b planes to target pixel type with JpegColorConverter
// 4. Convert color buffer to spectral blocks with component post processors int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1;
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
Span<float> rLane = this.redLane.GetSpan(); Span<float> rLane = this.redLane.GetSpan();
Span<float> gLane = this.greenLane.GetSpan(); Span<float> gLane = this.greenLane.GetSpan();
Span<float> bLane = this.blueLane.GetSpan(); Span<float> bLane = this.blueLane.GetSpan();
for (int yy = this.pixelRowCounter; yy < maxY; yy++) for (int yy = start; yy < end; yy++)
{ {
int y = yy - this.pixelRowCounter; int y = yy - this.pixelRowCounter;
// unpack TPixel to r/g/b planes // Unpack TPixel to r/g/b planes
Span<TPixel> sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex);
Span<TPixel> sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex);
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(this.configuration, rLane, gLane, bLane, sourceRow); PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow);
// Convert from rgb24 to target pixel type
var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y);
this.colorConverter.ConvertFromRgbInplace(values, rLane, gLane, bLane); this.colorConverter.ConvertFromRgbInplace(values, rLane, gLane, bLane);
} }
// Convert pixels to spectral
for (int i = 0; i < this.componentProcessors.Length; i++) for (int i = 0; i < this.componentProcessors.Length; i++)
{ {
this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep);
} }
this.pixelRowCounter += this.pixelRowsPerStep; this.pixelRowCounter = end;
}
/// <inheritdoc/>
public void Dispose()
{
foreach (ComponentProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
this.redLane.Dispose();
this.greenLane.Dispose();
this.blueLane.Dispose();
} }
} }
} }

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

@ -23,5 +23,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Non-interleaved encoding mode encodes each color component in a separate scan. /// Non-interleaved encoding mode encodes each color component in a separate scan.
/// </remarks> /// </remarks>
public bool? Interleaved { get; set; } public bool? Interleaved { get; set; }
/// <summary>
/// Gets or sets jpeg color for encoding.
/// </summary>
public JpegEncodingColor? ColorType { get; set; }
} }
} }

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

@ -564,17 +564,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
return JpegEncodingColor.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[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{ {
return JpegEncodingColor.YCbCrRatio420; return JpegEncodingColor.YCbCrRatio422;
} }
else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && 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 == 2 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2) this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{ {
return JpegEncodingColor.YCbCrRatio422; return JpegEncodingColor.YCbCrRatio420;
} }
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && 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[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
@ -595,7 +595,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegColorSpace.Cmyk: case JpegColorSpace.Cmyk:
return JpegEncodingColor.Cmyk; return JpegEncodingColor.Cmyk;
case JpegColorSpace.Ycck:
// TODO: change this after YccK encoding is implemented
// We are deliberately mapping YccK color space to Cmyk color space at metadata
// level so encoder can fallback to cmyk color space from it.
// YccK -> Cmyk is the closest conversion logically wise
return JpegEncodingColor.Cmyk;
default: default:
return JpegEncodingColor.YCbCrRatio420; return JpegEncodingColor.YCbCrRatio420;
} }

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

@ -5,7 +5,6 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -17,36 +16,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{ {
/// <summary> /// <summary>
/// The available encodable frame configs. /// Backing field for <see cref="Quality"/>.
/// </summary> /// </summary>
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); private int? quality;
/// <inheritdoc/> /// <inheritdoc/>
public int? Quality { get; set; } public int? Quality
/// <inheritdoc/>
public bool? Interleaved { get; set; }
/// <summary>
/// Sets jpeg color for encoding.
/// </summary>
public JpegEncodingColor ColorType
{ {
get => this.quality;
set set
{ {
JpegFrameConfig frameConfig = Array.Find( if (value is < 1 or > 100)
FrameConfigs,
cfg => cfg.EncodingColor == value);
if (frameConfig is null)
{ {
throw new ArgumentException(nameof(value)); throw new ArgumentException("Quality factor must be in [1..100] range.");
} }
this.FrameConfig = frameConfig; this.quality = value;
} }
} }
/// <inheritdoc/>
public bool? Interleaved { get; set; }
/// <inheritdoc/>
public JpegEncodingColor? ColorType { get; set; }
internal JpegFrameConfig FrameConfig { get; set; } internal JpegFrameConfig FrameConfig { get; set; }
/// <summary> /// <summary>
@ -58,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public void Encode<TPixel>(Image<TPixel> image, Stream stream) public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this, this.FrameConfig); var encoder = new JpegEncoderCore(this);
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
@ -73,249 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this, this.FrameConfig); var encoder = new JpegEncoderCore(this);
return encoder.EncodeAsync(image, stream, cancellationToken); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
private static JpegFrameConfig[] CreateFrameConfigs()
{
var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[0]);
var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[1]);
var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[2]);
var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[3]);
var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Block8x8.Load(Quantization.LuminanceTable));
var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Block8x8.Load(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
}),
// 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
}),
};
}
}
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; }
}
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; }
}
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; }
}
internal class JpegQuantizationTableConfig
{
public JpegQuantizationTableConfig(int destIndex, Block8x8 table)
{
this.DestinationIndex = destIndex;
this.Table = table;
}
public int DestinationIndex { get; }
public Block8x8 Table { get; }
} }
} }

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

@ -24,27 +24,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals
{ {
/// <summary> /// <summary>
/// The number of quantization tables. /// The available encodable frame configs.
/// </summary> /// </summary>
private const int QuantizationTableCount = 2; private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
/// <summary> /// <summary>
/// A scratch buffer to reduce allocations. /// A scratch buffer to reduce allocations.
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[20]; private readonly byte[] buffer = new byte[20];
/// <summary> private readonly IJpegEncoderOptions options;
/// The quality, that will be used to encode the image.
/// </summary>
private readonly int? quality;
private readonly bool? interleaved;
private JpegEncodingColor? colorType;
private JpegFrameConfig frameConfig;
private HuffmanScanEncoder scanEncoder;
/// <summary> /// <summary>
/// The output stream. All attempted writes after the first error become no-ops. /// The output stream. All attempted writes after the first error become no-ops.
@ -55,15 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class. /// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="frameConfig">Frame config.</param> public JpegEncoderCore(IJpegEncoderOptions options)
public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) => this.options = options;
{
this.quality = options.Quality;
this.interleaved = options.Interleaved;
this.frameConfig = frameConfig;
this.colorType = frameConfig.EncodingColor;
}
public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4];
@ -87,68 +69,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, this.frameConfig.ColorType);
this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream);
this.outputStream = stream; this.outputStream = stream;
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata);
bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true;
using var frame = new JpegFrame(image, frameConfig, interleaved);
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteStartOfImage(); this.WriteStartOfImage();
// Do not write APP0 marker for RGB colorspace. // Write APP0 marker for any non-RGB colorspace image.
if (this.colorType != JpegEncodingColor.Rgb) if (frameConfig.EncodingColor != JpegEncodingColor.Rgb)
{ {
this.WriteJfifApplicationHeader(metadata); this.WriteJfifApplicationHeader(metadata);
} }
// Write Exif, XMP, ICC and IPTC profiles // Else write App14 marker to indicate RGB color space.
this.WriteProfiles(metadata); else
if (this.colorType == JpegEncodingColor.Rgb)
{ {
// Write App14 marker to indicate RGB color space.
this.WriteApp14Marker(); this.WriteApp14Marker();
} }
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata);
// Write the image dimensions. // Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); this.WriteStartOfFrame(image.Width, image.Height, frameConfig);
// Write the Huffman tables. // Write the Huffman tables.
this.WriteDefineHuffmanTables(this.frameConfig.HuffmanTables); var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream);
this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder);
// Write the quantization tables. // Write the quantization tables.
this.WriteDefineQuantizationTables(this.frameConfig.QuantizationTables, jpegMetadata); this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata);
var spectralConverter = new SpectralConverter<TPixel>(frame, image, this.QuantizationTables, Configuration.Default); // Write scans with actual pixel data
using var spectralConverter = new SpectralConverter<TPixel>(frame, image, this.QuantizationTables);
if (frame.Components.Length == 1) this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken);
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(this.frameConfig.Components);
this.scanEncoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken);
}
else if (this.interleaved ?? jpegMetadata.Interleaved ?? true)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(this.frameConfig.Components);
this.scanEncoder.EncodeScanBaselineInterleaved(this.frameConfig.EncodingColor, frame, spectralConverter, cancellationToken);
}
else
{
frame.AllocateComponents(fullScan: true);
spectralConverter.ConvertFull();
Span<JpegComponentConfig> components = this.frameConfig.Components;
for (int i = 0; i < frame.Components.Length; i++)
{
this.WriteStartOfScan(components.Slice(i, 1));
this.scanEncoder.EncodeScanBaseline(frame.Components[i], cancellationToken);
}
}
// Write the End Of Image marker. // Write the End Of Image marker.
this.WriteEndOfImageMarker(); this.WriteEndOfImageMarker();
@ -216,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Writes the Define Huffman Table marker and tables. /// Writes the Define Huffman Table marker and tables.
/// </summary> /// </summary>
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs) private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder)
{ {
if (tableConfigs is null) if (tableConfigs is null)
{ {
@ -240,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(tableConfig.Table.Count); this.outputStream.Write(tableConfig.Table.Count);
this.outputStream.Write(tableConfig.Table.Values); this.outputStream.Write(tableConfig.Table.Values);
this.scanEncoder.BuildHuffmanTable(tableConfig); scanEncoder.BuildHuffmanTable(tableConfig);
} }
} }
@ -629,6 +589,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(this.buffer, 0, 2); 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> /// <summary>
/// Writes the header for a marker with the given length. /// Writes the header for a marker with the given length.
/// </summary> /// </summary>
@ -656,8 +650,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </list> /// </list>
/// </remarks> /// </remarks>
/// <param name="configs">Quantization tables configs.</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="metadata">Jpeg metadata instance.</param>
private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata)
{ {
int dataLen = configs.Length * (1 + Block8x8.Size); int dataLen = configs.Length * (1 + Block8x8.Size);
@ -668,11 +663,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
byte[] buffer = new byte[dataLen]; byte[] buffer = new byte[dataLen];
int offset = 0; int offset = 0;
Block8x8F workspaceBlock = default;
for (int i = 0; i < configs.Length; i++) for (int i = 0; i < configs.Length; i++)
{ {
JpegQuantizationTableConfig config = configs[i]; JpegQuantizationTableConfig config = configs[i];
int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata); int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata);
Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table);
// write to the output stream // write to the output stream
@ -683,8 +680,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]];
} }
// apply scaling and save into buffer // apply FDCT multipliers and inject to the destination index
this.QuantizationTables[config.DestinationIndex].LoadFromInt16Scalar(ref scaledTable); workspaceBlock.LoadFrom(ref scaledTable);
FastFloatingPointDCT.AdjustToFDCT(ref workspaceBlock);
this.QuantizationTables[config.DestinationIndex] = workspaceBlock;
} }
// write filled buffer to the stream // write filled buffer to the stream
@ -692,10 +692,177 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch
{ {
0 => encoderQuality ?? metadata.LuminanceQuality, 0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor,
1 => encoderQuality ?? metadata.ChrominanceQuality, 1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor,
_ => encoderQuality ?? metadata.Quality, _ => 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;
}
private static JpegFrameConfig[] CreateFrameConfigs()
{
var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[0]);
var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[1]);
var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[2]);
var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[3]);
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
}),
// 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
}),
};
}
} }
} }

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

@ -11,16 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public class JpegMetadata : IDeepCloneable 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> /// <summary>
/// Initializes a new instance of the <see cref="JpegMetadata"/> class. /// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary> /// </summary>
@ -36,8 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
this.ColorType = other.ColorType; this.ColorType = other.ColorType;
this.luminanceQuality = other.luminanceQuality; this.LuminanceQuality = other.LuminanceQuality;
this.chrominanceQuality = other.chrominanceQuality; this.ChrominanceQuality = other.ChrominanceQuality;
} }
/// <summary> /// <summary>
@ -47,11 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding /// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables. /// with non-complient ITU quantization tables.
/// </remarks> /// </remarks>
internal int LuminanceQuality internal int? LuminanceQuality { get; set; }
{
get => this.luminanceQuality ?? Quantization.DefaultQualityFactor;
set => this.luminanceQuality = value;
}
/// <summary> /// <summary>
/// Gets or sets the jpeg chrominance quality. /// Gets or sets the jpeg chrominance quality.
@ -60,11 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding /// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables. /// with non-complient ITU quantization tables.
/// </remarks> /// </remarks>
internal int ChrominanceQuality internal int? ChrominanceQuality { get; set; }
{
get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor;
set => this.chrominanceQuality = value;
}
/// <summary> /// <summary>
/// Gets the encoded quality. /// Gets the encoded quality.
@ -77,20 +59,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
get get
{ {
if (this.luminanceQuality.HasValue) if (this.LuminanceQuality.HasValue)
{ {
if (this.chrominanceQuality.HasValue) if (this.ChrominanceQuality.HasValue)
{ {
return Math.Max(this.luminanceQuality.Value, this.chrominanceQuality.Value); return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value);
} }
return this.luminanceQuality.Value; return this.LuminanceQuality.Value;
} }
else else
{ {
if (this.chrominanceQuality.HasValue) if (this.ChrominanceQuality.HasValue)
{ {
return this.chrominanceQuality.Value; return this.ChrominanceQuality.Value;
} }
return Quantization.DefaultQualityFactor; return Quantization.DefaultQualityFactor;

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

@ -39,12 +39,11 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc /> /// <inheritdoc />
internal override void UnpackIntoRgbPlanes( internal override void UnpackIntoRgbPlanes(
Configuration configuration, Span<float> redChannel,
ReadOnlySpan<float> redChannel, Span<float> greenChannel,
ReadOnlySpan<float> greenChannel, Span<float> blueChannel,
ReadOnlySpan<float> blueChannel, ReadOnlySpan<Rgb24> source)
Span<Rgb24> source) => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source);
=> SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source);
} }
} }
} }

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

@ -202,32 +202,27 @@ namespace SixLabors.ImageSharp.PixelFormats
/// Bulk operation that unpacks pixels from <paramref name="source"/> /// Bulk operation that unpacks pixels from <paramref name="source"/>
/// into 3 seperate RGB channels. The destination must have a padding of 3. /// into 3 seperate RGB channels. The destination must have a padding of 3.
/// </summary> /// </summary>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="redChannel">A <see cref="ReadOnlySpan{T}"/> to the red values.</param> /// <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="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="blueChannel">A <see cref="ReadOnlySpan{T}"/> to the blue values.</param>
/// <param name="source">A <see cref="Span{T}"/> to the destination pixels.</param> /// <param name="source">A <see cref="Span{T}"/> to the destination pixels.</param>
internal virtual void UnpackIntoRgbPlanes( internal virtual void UnpackIntoRgbPlanes(
Configuration configuration, Span<float> redChannel,
ReadOnlySpan<float> redChannel, Span<float> greenChannel,
ReadOnlySpan<float> greenChannel, Span<float> blueChannel,
ReadOnlySpan<float> blueChannel, ReadOnlySpan<TPixel> source)
Span<TPixel> source)
{ {
Guard.NotNull(configuration, nameof(configuration));
int count = redChannel.Length; int count = redChannel.Length;
Rgba32 rgba32 = default; Rgba32 rgba32 = default;
ref float r = ref MemoryMarshal.GetReference(redChannel); ref float r = ref MemoryMarshal.GetReference(redChannel);
ref float g = ref MemoryMarshal.GetReference(greenChannel); ref float g = ref MemoryMarshal.GetReference(greenChannel);
ref float b = ref MemoryMarshal.GetReference(blueChannel); ref float b = ref MemoryMarshal.GetReference(blueChannel);
ref TPixel d = ref MemoryMarshal.GetReference(source); ref TPixel src = ref MemoryMarshal.GetReference(source);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
// TODO: Create ToRgb24 method in IPixel Unsafe.Add(ref src, i).ToRgba32(ref rgba32);
Unsafe.Add(ref d, i).ToRgba32(ref rgba32);
Unsafe.Add(ref r, i) = rgba32.R; Unsafe.Add(ref r, i) = rgba32.R;
Unsafe.Add(ref g, i) = rgba32.G; Unsafe.Add(ref g, i) = rgba32.G;
Unsafe.Add(ref b, i) = rgba32.B; Unsafe.Add(ref b, i) = rgba32.B;

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromCmykScalar(8).ConvertToRgbInplace(values); new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInplace(values);
} }
[Benchmark] [Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromCmykVector(8).ConvertToRgbInplace(values); new JpegColorConverterBase.CmykVector(8).ConvertToRgbInplace(values);
} }
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromCmykAvx(8).ConvertToRgbInplace(values); new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values);
} }
#endif #endif
} }

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromGrayscaleScalar(8).ConvertToRgbInplace(values); new JpegColorConverterBase.GrayscaleScalar(8).ConvertToRgbInplace(values);
} }
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromGrayscaleAvx(8).ConvertToRgbInplace(values); new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values);
} }
#endif #endif
} }

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromRgbScalar(8).ConvertToRgbInplace(values); new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInplace(values);
} }
[Benchmark] [Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromRgbVector(8).ConvertToRgbInplace(values); new JpegColorConverterBase.RgbVector(8).ConvertToRgbInplace(values);
} }
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromRgbAvx(8).ConvertToRgbInplace(values); new JpegColorConverterBase.RgbAvx(8).ConvertToRgbInplace(values);
} }
#endif #endif
} }

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYCbCrScalar(8).ConvertToRgbInplace(values); new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInplace(values);
} }
[Benchmark] [Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYCbCrVector(8).ConvertToRgbInplace(values); new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInplace(values);
} }
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYCbCrAvx(8).ConvertToRgbInplace(values); new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values);
} }
#endif #endif
} }

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYccKScalar(8).ConvertToRgbInplace(values); new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInplace(values);
} }
[Benchmark] [Benchmark]
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYccKVector(8).ConvertToRgbInplace(values); new JpegColorConverterBase.YccKVector(8).ConvertToRgbInplace(values);
} }
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
new JpegColorConverterBase.FromYccKAvx(8).ConvertToRgbInplace(values); new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values);
} }
#endif #endif
} }

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

@ -22,8 +22,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
// No metadata // No metadata
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
public static IEnumerable<JpegEncodingColor> ColorSpaceValues => public static IEnumerable<JpegEncodingColor> ColorSpaceValues => new[]
new[] { JpegEncodingColor.Luminance, JpegEncodingColor.Rgb, JpegEncodingColor.YCbCrRatio420, JpegEncodingColor.YCbCrRatio444 }; {
JpegEncodingColor.Luminance,
JpegEncodingColor.Rgb,
JpegEncodingColor.YCbCrRatio420,
JpegEncodingColor.YCbCrRatio444,
};
[Params(75, 90, 100)] [Params(75, 90, 100)]
public int Quality; public int Quality;
@ -41,7 +46,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgb24>(imageBinaryStream); 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(); this.destinationStream = new MemoryStream();
} }
@ -67,23 +77,23 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
/* /*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 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 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 .NET SDK=6.0.202
[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT [Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
| Method | TargetColorSpace | Quality | Mean | Error | StdDev | | Method | TargetColorSpace | Quality | Mean | Error | StdDev |
|---------- |----------------- |-------- |----------:|----------:|----------:| |---------- |----------------- |-------- |----------:|----------:|----------:|
| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms | | Benchmark | Luminance | 75 | 4.575 ms | 0.0233 ms | 0.0207 ms |
| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms | | Benchmark | Rgb | 75 | 12.477 ms | 0.1051 ms | 0.0932 ms |
| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms | | Benchmark | YCbCrRatio420 | 75 | 6.421 ms | 0.0464 ms | 0.0434 ms |
| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms | | Benchmark | YCbCrRatio444 | 75 | 8.449 ms | 0.1246 ms | 0.1166 ms |
| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms | | Benchmark | Luminance | 90 | 4.863 ms | 0.0120 ms | 0.0106 ms |
| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms | | Benchmark | Rgb | 90 | 13.287 ms | 0.0548 ms | 0.0513 ms |
| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms | | Benchmark | YCbCrRatio420 | 90 | 7.012 ms | 0.0533 ms | 0.0499 ms |
| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms | | Benchmark | YCbCrRatio444 | 90 | 8.916 ms | 0.1285 ms | 0.1202 ms |
| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms | | Benchmark | Luminance | 100 | 6.665 ms | 0.0136 ms | 0.0113 ms |
| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms | | Benchmark | Rgb | 100 | 19.734 ms | 0.0477 ms | 0.0446 ms |
| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms | | Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0925 ms | 0.0865 ms |
| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms | | Benchmark | YCbCrRatio444 | 100 | 15.587 ms | 0.1695 ms | 0.1586 ms |
*/ */

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

@ -22,11 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private const int TestBufferLength = 40; private const int TestBufferLength = 40;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; 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); private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision);
@ -52,7 +48,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void GetConverterThrowsExceptionOnInvalidPrecision() public void GetConverterThrowsExceptionOnInvalidPrecision()
{ {
// Valid precisions: 8 & 12 bit // 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] [Theory]
@ -94,13 +91,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYCbCrBasic(int seed) => public void FromYCbCrBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed); this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYCbCrVector(int seed) public void FromYCbCrVector(int seed)
{ {
var converter = new JpegColorConverterBase.FromYCbCrVector(8); var converter = new JpegColorConverterBase.YCbCrVector(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -116,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversion(
new JpegColorConverterBase.FromYCbCrVector(8), new JpegColorConverterBase.YCbCrVector(8),
3, 3,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg));
} }
@ -124,13 +121,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromCmykBasic(int seed) => public void FromCmykBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed); this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromCmykVector(int seed) public void FromCmykVector(int seed)
{ {
var converter = new JpegColorConverterBase.FromCmykVector(8); var converter = new JpegColorConverterBase.CmykVector(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -146,7 +143,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversion(
new JpegColorConverterBase.FromCmykVector(8), new JpegColorConverterBase.CmykVector(8),
4, 4,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg));
} }
@ -154,13 +151,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromGrayscaleBasic(int seed) => public void FromGrayscaleBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed); this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromGrayscaleVector(int seed) public void FromGrayscaleVector(int seed)
{ {
var converter = new JpegColorConverterBase.FromGrayScaleVector(8); var converter = new JpegColorConverterBase.GrayScaleVector(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -176,7 +173,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversion(
new JpegColorConverterBase.FromGrayScaleVector(8), new JpegColorConverterBase.GrayScaleVector(8),
1, 1,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg));
} }
@ -184,13 +181,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromRgbBasic(int seed) => public void FromRgbBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed); this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromRgbVector(int seed) public void FromRgbVector(int seed)
{ {
var converter = new JpegColorConverterBase.FromRgbVector(8); var converter = new JpegColorConverterBase.RgbVector(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -206,7 +203,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversion(
new JpegColorConverterBase.FromRgbVector(8), new JpegColorConverterBase.RgbVector(8),
3, 3,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg));
} }
@ -214,13 +211,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYccKBasic(int seed) => public void FromYccKBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed); this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYccKVector(int seed) public void FromYccKVector(int seed)
{ {
var converter = new JpegColorConverterBase.FromYccKVector(8); var converter = new JpegColorConverterBase.YccKVector(8);
if (!converter.IsAvailable) if (!converter.IsAvailable)
{ {
@ -236,37 +233,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) => static void RunTest(string arg) =>
ValidateConversion( ValidateConversion(
new JpegColorConverterBase.FromYccKVector(8), new JpegColorConverterBase.YccKVector(8),
4, 4,
FeatureTestRunner.Deserialize<int>(arg)); FeatureTestRunner.Deserialize<int>(arg));
} }
#if SUPPORTS_RUNTIME_INTRINSICS
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYCbCrAvx2(int seed) => public void FromYCbCrAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromCmykAvx2(int seed) => public void FromCmykAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromGrayscaleAvx2(int seed) => public void FromGrayscaleAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) => public void FromRgbAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed);
[Theory] [Theory]
[MemberData(nameof(Seeds))] [MemberData(nameof(Seeds))]
public void FromYccKAvx2(int seed) => public void FromYccKAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed); this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed);
#endif
private void TestConverter( private void TestConverter(
JpegColorConverterBase converter, JpegColorConverterBase converter,

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

@ -145,7 +145,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)] [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)] [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)] [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegEncodingColor.YCbCrRatio422)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)]
public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType)
{ {

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

@ -85,53 +85,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expectedColorType, meta.ColorType); Assert.Equal(expectedColorType, meta.ColorType);
} }
[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>
{
// arrange
using Image<TPixel> input = provider.GetImage(JpegDecoder);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, new JpegEncoder()
{
Quality = 75
});
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(JpegEncodingColor.YCbCrRatio420, meta.ColorType);
}
[Theory]
[InlineData(JpegEncodingColor.Cmyk)]
[InlineData(JpegEncodingColor.YCbCrRatio410)]
[InlineData(JpegEncodingColor.YCbCrRatio411)]
[InlineData(JpegEncodingColor.YCbCrRatio422)]
public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegEncodingColor 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(JpegEncodingColor.YCbCrRatio420, meta.ColorType);
}
[Theory] [Theory]
[MemberData(nameof(QualityFiles))] [MemberData(nameof(QualityFiles))]
public void Encode_PreservesQuality(string imagePath, int quality) public void Encode_PreservesQuality(string imagePath, int quality)
@ -178,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality) public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.15f));
[Theory] [Theory]
[WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)] [WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)]
@ -265,34 +218,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
} }
[Fact] [Fact]
public void Quality_0_And_1_Are_Identical() public void Quality_1_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 = 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 var options = new JpegEncoder
{ {
Quality = 0 Quality = 1
}; };
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);

Loading…
Cancel
Save