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(
ref ReadOnlySpan<float> redChannel,
ref ReadOnlySpan<float> greenChannel,
ref ReadOnlySpan<float> blueChannel,
ref Span<Rgb24> source)
ref Span<float> redChannel,
ref Span<float> greenChannel,
ref Span<float> blueChannel,
ref ReadOnlySpan<Rgb24> source)
{
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(source));
ref Vector256<float> destRRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(redChannel));

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

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

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

@ -1,10 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable UseObjectOrCollectionInitializer
// 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 sealed class FromCmykAvx : JpegColorConverterAvx
internal sealed class CmykAvx : JpegColorConverterAvx
{
public FromCmykAvx(int precision)
public CmykAvx(int 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 sealed class FromCmykScalar : JpegColorConverterScalar
internal sealed class CmykScalar : JpegColorConverterScalar
{
public FromCmykScalar(int precision)
public CmykScalar(int 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 sealed class FromCmykVector : JpegColorConverterVector
internal sealed class CmykVector : JpegColorConverterVector
{
public FromCmykVector(int precision)
public CmykVector(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
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)
{
@ -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)
=> 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 sealed class FromGrayscaleAvx : JpegColorConverterAvx
internal sealed class GrayscaleAvx : JpegColorConverterAvx
{
public FromGrayscaleAvx(int precision)
public GrayscaleAvx(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = Avx.Multiply(c0, scale);
}
}
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.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
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)
{
}
@ -22,6 +24,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
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)
@ -35,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
float b = bLane[i];
// 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 sealed class FromGrayScaleVector : JpegColorConverterVector
internal sealed class GrayScaleVector : JpegColorConverterVector
{
public FromGrayScaleVector(int precision)
public GrayScaleVector(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
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)
{
@ -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)
=> 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 sealed class FromRgbAvx : JpegColorConverterAvx
internal sealed class RgbAvx : JpegColorConverterAvx
{
public FromRgbAvx(int precision)
public RgbAvx(int 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 sealed class FromRgbScalar : JpegColorConverterScalar
internal sealed class RgbScalar : JpegColorConverterScalar
{
public FromRgbScalar(int precision)
public RgbScalar(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue)
{
FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue);
FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue);
FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue);
GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue);
GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue);
GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue);
}
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 sealed class FromRgbVector : JpegColorConverterVector
internal sealed class RgbVector : JpegColorConverterVector
{
public FromRgbVector(int precision)
public RgbVector(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
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)
{
@ -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)
=> 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 sealed class FromYCbCrAvx : JpegColorConverterAvx
internal sealed class YCbCrAvx : JpegColorConverterAvx
{
public FromYCbCrAvx(int precision)
public YCbCrAvx(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
@ -33,10 +33,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult);
var rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count;

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 sealed class FromYCbCrScalar : JpegColorConverterScalar
internal sealed class YCbCrScalar : JpegColorConverterScalar
{
// TODO: comments, derived from ITU-T Rec. T.871
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 BCbMult = 1.772f;
public FromYCbCrScalar(int precision)
public YCbCrScalar(int 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 sealed class FromYCbCrVector : JpegColorConverterVector
internal sealed class YCbCrVector : JpegColorConverterVector
{
public FromYCbCrVector(int precision)
public YCbCrVector(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
@ -30,10 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult);
var rCrMult = new Vector<float>(YCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-YCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-YCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(YCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
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)
{
@ -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)
=> 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 sealed class FromYccKAvx : JpegColorConverterAvx
internal sealed class YccKAvx : JpegColorConverterAvx
{
public FromYccKAvx(int precision)
public YccKAvx(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
@ -35,10 +35,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult);
var rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / Vector256<float>.Count;

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 sealed class FromYccKScalar : JpegColorConverterScalar
internal sealed class YccKScalar : JpegColorConverterScalar
{
public FromYccKScalar(int precision)
public YccKScalar(int 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 sealed class FromYccKVector : JpegColorConverterVector
internal sealed class YccKVector : JpegColorConverterVector
{
public FromYccKVector(int precision)
public YccKVector(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
@ -31,10 +31,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var chromaOffset = new Vector<float>(-this.HalfValue);
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
var max = new Vector<float>(this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult);
var rCrMult = new Vector<float>(YCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-YCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-YCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(YCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
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)
=> throw new System.NotImplementedException();

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

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

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>
/// Represents a single frame component.
/// </summary>
internal class JpegComponent : IDisposable
internal class Component : IDisposable
{
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;

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
{
internal class JpegComponentPostProcessor : IDisposable
internal class ComponentProcessor : IDisposable
{
private readonly Size blockAreaSize;
private readonly JpegComponent component;
private readonly Component component;
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.quantTable = quantTable;
FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable);
this.component = component;
this.blockAreaSize = component.SubSamplingDivisors * 8;
// alignment of 8 so each block stride can be sampled from a single 'ref pointer'
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
8 * component.SubSamplingDivisors.Height,
8,
AllocationOptions.Clean);
}
@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// but 12-bit jpegs are not supported currently
float normalizationValue = -128f;
int destAreaStride = this.ColorBuffer.Width * this.component.SubSamplingDivisors.Height;
int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = spectralStep * this.component.SamplingFactors.Height;
@ -97,22 +98,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
Size factors = this.component.SubSamplingDivisors;
int packedWidth = this.ColorBuffer.Width / factors.Width;
float averageMultiplier = 1f / (factors.Width * factors.Height);
for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height)
{
Span<float> targetBufferRow = this.ColorBuffer.DangerousGetRowSpan(i);
Span<float> sourceRow = this.ColorBuffer.DangerousGetRowSpan(i);
// vertical sum
for (int j = 1; j < factors.Height; j++)
{
SumVertical(targetBufferRow, this.ColorBuffer.DangerousGetRowSpan(i + j));
SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j));
}
// horizontal sum
SumHorizontal(targetBufferRow, factors.Width);
SumHorizontal(sourceRow, factors.Width);
// 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)

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);
}
/// <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)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -142,20 +149,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
case JpegEncodingColor.YCbCrRatio444:
case JpegEncodingColor.Rgb:
this.EncodeScanBaselineInterleaved444(frame, converter, cancellationToken);
break;
case JpegEncodingColor.YCbCrRatio420:
this.EncodeScanBaselineInterleaved420(frame, converter, cancellationToken);
this.EncodeThreeComponentScanBaselineInterleaved444(frame, converter, cancellationToken);
break;
default:
this.EncodeScanBaselineInterleavedArbitrarySampling(frame, converter, cancellationToken);
this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken);
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>
{
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 w = component.WidthInBlocks;
@ -225,7 +240,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
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>
{
int mcu = 0;
@ -246,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int mcuCol = mcu % mcusPerLine;
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 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>
{
int mcusPerColumn = frame.McusPerColumn;
int mcusPerLine = frame.McusPerLine;
JpegComponent c2 = frame.Components[2];
JpegComponent c1 = frame.Components[1];
JpegComponent c0 = frame.Components[0];
Component c2 = frame.Components[2];
Component c1 = frame.Components[1];
Component c0 = frame.Components[0];
ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId];
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)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
this.FlushRemainingBytes();
}
private void WriteBlock(
JpegComponent component,
Component component,
ref Block8x8 block,
ref HuffmanLut dcTable,
ref HuffmanLut acTable)
@ -611,7 +636,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Flushes spectral data bytes after encoding all channel blocks
/// in a single jpeg macroblock using <see cref="WriteBlock(JpegComponent, ref Block8x8, ref HuffmanLut, ref HuffmanLut)"/>.
/// in a single jpeg macroblock using <see cref="WriteBlock(Component, ref Block8x8, ref HuffmanLut, ref HuffmanLut)"/>.
/// </summary>
/// <remarks>
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
@ -641,7 +666,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount;
this.FlushToStream(lastByteIndex);
// Clean huffman register
// Clear huffman register
// This is needed for for images with multiples scans
this.bitCount = 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.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@ -11,19 +12,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
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.PixelHeight = image.Height;
MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator;
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++)
{
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,
AcTableId = componentConfig.AcTableSelector,
@ -39,18 +44,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
for (int i = 0; i < this.Components.Length; i++)
{
JpegComponent component = this.Components[i];
Component component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV);
}
}
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; }
@ -62,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
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++)
{
JpegComponent component = this.Components[i];
Component component = this.Components[i];
component.AllocateSpectral(fullScan);
}
}

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

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

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

@ -5,7 +5,6 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats;
@ -17,36 +16,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{
/// <summary>
/// The available encodable frame configs.
/// Backing field for <see cref="Quality"/>.
/// </summary>
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
private int? quality;
/// <inheritdoc/>
public int? Quality { get; set; }
/// <inheritdoc/>
public bool? Interleaved { get; set; }
/// <summary>
/// Sets jpeg color for encoding.
/// </summary>
public JpegEncodingColor ColorType
public int? Quality
{
get => this.quality;
set
{
JpegFrameConfig frameConfig = Array.Find(
FrameConfigs,
cfg => cfg.EncodingColor == value);
if (frameConfig is null)
if (value is < 1 or > 100)
{
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; }
/// <summary>
@ -58,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new JpegEncoderCore(this, this.FrameConfig);
var encoder = new JpegEncoderCore(this);
encoder.Encode(image, stream);
}
@ -73,249 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new JpegEncoderCore(this, this.FrameConfig);
var encoder = new JpegEncoderCore(this);
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
{
/// <summary>
/// The number of quantization tables.
/// The available encodable frame configs.
/// </summary>
private const int QuantizationTableCount = 2;
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[20];
/// <summary>
/// The quality, that will be used to encode the image.
/// </summary>
private readonly int? quality;
private readonly bool? interleaved;
private JpegEncodingColor? colorType;
private JpegFrameConfig frameConfig;
private HuffmanScanEncoder scanEncoder;
private readonly IJpegEncoderOptions options;
/// <summary>
/// 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.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="frameConfig">Frame config.</param>
public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig)
{
this.quality = options.Quality;
this.interleaved = options.Interleaved;
this.frameConfig = frameConfig;
this.colorType = frameConfig.EncodingColor;
}
public JpegEncoderCore(IJpegEncoderOptions options)
=> this.options = options;
public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4];
@ -87,68 +69,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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;
ImageMetadata metadata = image.Metadata;
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.
this.WriteStartOfImage();
// Do not write APP0 marker for RGB colorspace.
if (this.colorType != JpegEncodingColor.Rgb)
// Write APP0 marker for any non-RGB colorspace image.
if (frameConfig.EncodingColor != JpegEncodingColor.Rgb)
{
this.WriteJfifApplicationHeader(metadata);
}
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata);
if (this.colorType == JpegEncodingColor.Rgb)
// Else write App14 marker to indicate RGB color space.
else
{
// Write App14 marker to indicate RGB color space.
this.WriteApp14Marker();
}
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig);
this.WriteStartOfFrame(image.Width, image.Height, frameConfig);
// 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.
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);
if (frame.Components.Length == 1)
{
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 scans with actual pixel data
using var spectralConverter = new SpectralConverter<TPixel>(frame, image, this.QuantizationTables);
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken);
// Write the End Of Image marker.
this.WriteEndOfImageMarker();
@ -216,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the Define Huffman Table marker and tables.
/// </summary>
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs)
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder)
{
if (tableConfigs is null)
{
@ -240,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(tableConfig.Table.Count);
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);
}
/// <summary>
/// Writes scans for given config.
/// </summary>
private void WriteHuffmanScans<TPixel>(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter<TPixel> spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
if (frame.Components.Length == 1)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(frameConfig.Components);
encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken);
}
else if (frame.Interleaved)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(frameConfig.Components);
encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken);
}
else
{
frame.AllocateComponents(fullScan: true);
spectralConverter.ConvertFull();
Span<JpegComponentConfig> components = frameConfig.Components;
for (int i = 0; i < frame.Components.Length; i++)
{
this.WriteStartOfScan(components.Slice(i, 1));
encoder.EncodeScanBaseline(frame.Components[i], cancellationToken);
}
}
}
/// <summary>
/// Writes the header for a marker with the given length.
/// </summary>
@ -656,8 +650,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </list>
/// </remarks>
/// <param name="configs">Quantization tables configs.</param>
/// <param name="optionsQuality">Optional quality value from the options.</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);
@ -668,11 +663,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
byte[] buffer = new byte[dataLen];
int offset = 0;
Block8x8F workspaceBlock = default;
for (int i = 0; i < configs.Length; 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);
// write to the output stream
@ -683,8 +680,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]];
}
// apply scaling and save into buffer
this.QuantizationTables[config.DestinationIndex].LoadFromInt16Scalar(ref scaledTable);
// apply FDCT multipliers and inject to the destination index
workspaceBlock.LoadFrom(ref scaledTable);
FastFloatingPointDCT.AdjustToFDCT(ref workspaceBlock);
this.QuantizationTables[config.DestinationIndex] = workspaceBlock;
}
// 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
{
0 => encoderQuality ?? metadata.LuminanceQuality,
1 => encoderQuality ?? metadata.ChrominanceQuality,
0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor,
1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor,
_ => encoderQuality ?? metadata.Quality,
};
}
private JpegFrameConfig GetFrameConfig(JpegMetadata metadata)
{
JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420;
JpegFrameConfig frameConfig = Array.Find(
FrameConfigs,
cfg => cfg.EncodingColor == color);
if (frameConfig == null)
{
throw new ArgumentException(nameof(color));
}
return frameConfig;
}
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>
public class JpegMetadata : IDeepCloneable
{
/// <summary>
/// Backing field for <see cref="LuminanceQuality"/>
/// </summary>
private int? luminanceQuality;
/// <summary>
/// Backing field for <see cref="ChrominanceQuality"/>
/// </summary>
private int? chrominanceQuality;
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary>
@ -36,8 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
this.ColorType = other.ColorType;
this.luminanceQuality = other.luminanceQuality;
this.chrominanceQuality = other.chrominanceQuality;
this.LuminanceQuality = other.LuminanceQuality;
this.ChrominanceQuality = other.ChrominanceQuality;
}
/// <summary>
@ -47,11 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int LuminanceQuality
{
get => this.luminanceQuality ?? Quantization.DefaultQualityFactor;
set => this.luminanceQuality = value;
}
internal int? LuminanceQuality { get; set; }
/// <summary>
/// Gets or sets the jpeg chrominance quality.
@ -60,11 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int ChrominanceQuality
{
get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor;
set => this.chrominanceQuality = value;
}
internal int? ChrominanceQuality { get; set; }
/// <summary>
/// Gets the encoded quality.
@ -77,20 +59,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
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
{
if (this.chrominanceQuality.HasValue)
if (this.ChrominanceQuality.HasValue)
{
return this.chrominanceQuality.Value;
return this.ChrominanceQuality.Value;
}
return Quantization.DefaultQualityFactor;

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

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

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

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

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

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

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

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

@ -22,8 +22,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
// No metadata
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
public static IEnumerable<JpegEncodingColor> ColorSpaceValues =>
new[] { JpegEncodingColor.Luminance, JpegEncodingColor.Rgb, JpegEncodingColor.YCbCrRatio420, JpegEncodingColor.YCbCrRatio444 };
public static IEnumerable<JpegEncodingColor> ColorSpaceValues => new[]
{
JpegEncodingColor.Luminance,
JpegEncodingColor.Rgb,
JpegEncodingColor.YCbCrRatio420,
JpegEncodingColor.YCbCrRatio444,
};
[Params(75, 90, 100)]
public int Quality;
@ -41,7 +46,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgb24>(imageBinaryStream);
this.encoder = new JpegEncoder { Quality = this.Quality, ColorType = this.TargetColorSpace };
this.encoder = new JpegEncoder
{
Quality = this.Quality,
ColorType = this.TargetColorSpace,
Interleaved = true,
};
this.destinationStream = new MemoryStream();
}
@ -67,23 +77,23 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
.NET SDK=6.0.202
[Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT
| Method | TargetColorSpace | Quality | Mean | Error | StdDev |
|---------- |----------------- |-------- |----------:|----------:|----------:|
| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms |
| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms |
| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms |
| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms |
| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms |
| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms |
| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms |
| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms |
| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms |
| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms |
| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms |
| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms |
| Benchmark | Luminance | 75 | 4.575 ms | 0.0233 ms | 0.0207 ms |
| Benchmark | Rgb | 75 | 12.477 ms | 0.1051 ms | 0.0932 ms |
| Benchmark | YCbCrRatio420 | 75 | 6.421 ms | 0.0464 ms | 0.0434 ms |
| Benchmark | YCbCrRatio444 | 75 | 8.449 ms | 0.1246 ms | 0.1166 ms |
| Benchmark | Luminance | 90 | 4.863 ms | 0.0120 ms | 0.0106 ms |
| Benchmark | Rgb | 90 | 13.287 ms | 0.0548 ms | 0.0513 ms |
| Benchmark | YCbCrRatio420 | 90 | 7.012 ms | 0.0533 ms | 0.0499 ms |
| Benchmark | YCbCrRatio444 | 90 | 8.916 ms | 0.1285 ms | 0.1202 ms |
| Benchmark | Luminance | 100 | 6.665 ms | 0.0136 ms | 0.0113 ms |
| Benchmark | Rgb | 100 | 19.734 ms | 0.0477 ms | 0.0446 ms |
| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0925 ms | 0.0865 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;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX;
#else
private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll;
#endif
private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision);
@ -52,7 +48,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void GetConverterThrowsExceptionOnInvalidPrecision()
{
// Valid precisions: 8 & 12 bit
Assert.Throws<Exception>(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 9));
int invalidPrecision = 9;
Assert.Throws<Exception>(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision));
}
[Theory]
@ -94,13 +91,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrVector(int seed)
{
var converter = new JpegColorConverterBase.FromYCbCrVector(8);
var converter = new JpegColorConverterBase.YCbCrVector(8);
if (!converter.IsAvailable)
{
@ -116,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromYCbCrVector(8),
new JpegColorConverterBase.YCbCrVector(8),
3,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -124,13 +121,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed);
this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykVector(int seed)
{
var converter = new JpegColorConverterBase.FromCmykVector(8);
var converter = new JpegColorConverterBase.CmykVector(8);
if (!converter.IsAvailable)
{
@ -146,7 +143,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromCmykVector(8),
new JpegColorConverterBase.CmykVector(8),
4,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -154,13 +151,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed);
this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleVector(int seed)
{
var converter = new JpegColorConverterBase.FromGrayScaleVector(8);
var converter = new JpegColorConverterBase.GrayScaleVector(8);
if (!converter.IsAvailable)
{
@ -176,7 +173,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromGrayScaleVector(8),
new JpegColorConverterBase.GrayScaleVector(8),
1,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -184,13 +181,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbVector(int seed)
{
var converter = new JpegColorConverterBase.FromRgbVector(8);
var converter = new JpegColorConverterBase.RgbVector(8);
if (!converter.IsAvailable)
{
@ -206,7 +203,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromRgbVector(8),
new JpegColorConverterBase.RgbVector(8),
3,
FeatureTestRunner.Deserialize<int>(arg));
}
@ -214,13 +211,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKBasic(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed);
this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKVector(int seed)
{
var converter = new JpegColorConverterBase.FromYccKVector(8);
var converter = new JpegColorConverterBase.YccKVector(8);
if (!converter.IsAvailable)
{
@ -236,37 +233,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
static void RunTest(string arg) =>
ValidateConversion(
new JpegColorConverterBase.FromYccKVector(8),
new JpegColorConverterBase.YccKVector(8),
4,
FeatureTestRunner.Deserialize<int>(arg));
}
#if SUPPORTS_RUNTIME_INTRINSICS
[Theory]
[MemberData(nameof(Seeds))]
public void FromYCbCrAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromCmykAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed);
this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed);
this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed);
this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed);
[Theory]
[MemberData(nameof(Seeds))]
public void FromYccKAvx2(int seed) =>
this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed);
#endif
this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed);
private void TestConverter(
JpegColorConverterBase converter,

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.Cmyk, JpegEncodingColor.Cmyk)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegEncodingColor.YCbCrRatio422)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)]
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);
}
[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]
[MemberData(nameof(QualityFiles))]
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), 51, 7, PixelTypes.Rgba32)]
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]
[WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)]
@ -265,34 +218,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Fact]
public void Quality_0_And_1_Are_Identical()
{
var options = new JpegEncoder
{
Quality = 0
};
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
using (var memStream0 = new MemoryStream())
using (var memStream1 = new MemoryStream())
{
input.SaveAsJpeg(memStream0, options);
options.Quality = 1;
input.SaveAsJpeg(memStream1, options);
Assert.Equal(memStream0.ToArray(), memStream1.ToArray());
}
}
[Fact]
public void Quality_0_And_100_Are_Not_Identical()
public void Quality_1_And_100_Are_Not_Identical()
{
var options = new JpegEncoder
{
Quality = 0
Quality = 1
};
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);

Loading…
Cancel
Save