Browse Source

refactor subsampling + better IDCT constants in actual implementation

pull/322/head
Anton Firszov 9 years ago
parent
commit
0614e3fe3a
  1. 24
      src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs
  2. 68
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/SubsampleRatio.cs
  3. 62
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs
  4. 30
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  5. 16
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  6. 32
      tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs

24
src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs

@ -13,29 +13,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
internal static class FastFloatingPointDCT
{
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private static readonly float C_1_175876 = 1.175876f;
private static readonly float C_1_175876 = 1.175875602f;
private static readonly float C_1_961571 = -1.961571f;
private static readonly float C_1_961571 = -1.961570560f;
private static readonly float C_0_390181 = -0.390181f;
private static readonly float C_0_390181 = -0.390180644f;
private static readonly float C_0_899976 = -0.899976f;
private static readonly float C_0_899976 = -0.899976223f;
private static readonly float C_2_562915 = -2.562915f;
private static readonly float C_2_562915 = -2.562915447f;
private static readonly float C_0_298631 = 0.298631f;
private static readonly float C_0_298631 = 0.298631336f;
private static readonly float C_2_053120 = 2.053120f;
private static readonly float C_2_053120 = 2.053119869f;
private static readonly float C_3_072711 = 3.072711f;
private static readonly float C_3_072711 = 3.072711026f;
private static readonly float C_1_501321 = 1.501321f;
private static readonly float C_1_501321 = 1.501321110f;
private static readonly float C_0_541196 = 0.541196f;
private static readonly float C_0_541196 = 0.541196100f;
private static readonly float C_1_847759 = -1.847759f;
private static readonly float C_1_847759 = -1.847759065f;
private static readonly float C_0_765367 = 0.765367f;
private static readonly float C_0_765367 = 0.765366865f;
private static readonly float C_0_125 = 0.1250f;
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore

68
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/SubsampleRatio.cs

@ -0,0 +1,68 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Provides enumeration of the various available subsample ratios.
/// https://en.wikipedia.org/wiki/Chroma_subsampling
/// </summary>
internal enum SubsampleRatio
{
Undefined,
/// <summary>
/// 4:4:4
/// </summary>
Ratio444,
/// <summary>
/// 4:2:2
/// </summary>
Ratio422,
/// <summary>
/// 4:2:0
/// </summary>
Ratio420,
/// <summary>
/// 4:4:0
/// </summary>
Ratio440,
/// <summary>
/// 4:1:1
/// </summary>
Ratio411,
/// <summary>
/// 4:1:0
/// </summary>
Ratio410,
}
/// <summary>
/// Various utilities for <see cref="SubsampleRatio"/>
/// </summary>
internal static class Subsampling
{
public static SubsampleRatio GetSubsampleRatio(int horizontalRatio, int verticalRatio)
{
switch ((horizontalRatio << 4) | verticalRatio)
{
case 0x11:
return SubsampleRatio.Ratio444;
case 0x12:
return SubsampleRatio.Ratio440;
case 0x21:
return SubsampleRatio.Ratio422;
case 0x22:
return SubsampleRatio.Ratio420;
case 0x41:
return SubsampleRatio.Ratio411;
case 0x42:
return SubsampleRatio.Ratio410;
}
return SubsampleRatio.Ratio444;
}
}
}

62
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="ratio">The ratio.</param>
public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio)
public YCbCrImage(int width, int height, SubsampleRatio ratio)
{
Size cSize = CalculateChrominanceSize(width, height, ratio);
@ -49,42 +49,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.CrChannel = Buffer2D<byte>.CreateClean(cSize.Width, cSize.Height);
}
/// <summary>
/// Provides enumeration of the various available subsample ratios.
/// </summary>
public enum YCbCrSubsampleRatio
{
/// <summary>
/// YCbCrSubsampleRatio444
/// </summary>
YCbCrSubsampleRatio444,
/// <summary>
/// YCbCrSubsampleRatio422
/// </summary>
YCbCrSubsampleRatio422,
/// <summary>
/// YCbCrSubsampleRatio420
/// </summary>
YCbCrSubsampleRatio420,
/// <summary>
/// YCbCrSubsampleRatio440
/// </summary>
YCbCrSubsampleRatio440,
/// <summary>
/// YCbCrSubsampleRatio411
/// </summary>
YCbCrSubsampleRatio411,
/// <summary>
/// YCbCrSubsampleRatio410
/// </summary>
YCbCrSubsampleRatio410,
}
/// <summary>
/// Gets the Y slice index delta between vertically adjacent pixels.
/// </summary>
@ -99,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Gets or sets the subsampling ratio.
/// </summary>
public YCbCrSubsampleRatio Ratio { get; set; }
public SubsampleRatio Ratio { get; set; }
/// <summary>
/// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools.
@ -122,15 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
switch (this.Ratio)
{
case YCbCrSubsampleRatio.YCbCrSubsampleRatio422:
case SubsampleRatio.Ratio422:
return y * this.CStride;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio420:
case SubsampleRatio.Ratio420:
return (y / 2) * this.CStride;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio440:
case SubsampleRatio.Ratio440:
return (y / 2) * this.CStride;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio411:
case SubsampleRatio.Ratio411:
return y * this.CStride;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio410:
case SubsampleRatio.Ratio410:
return (y / 2) * this.CStride;
default:
return y * this.CStride;
@ -159,19 +123,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
internal static Size CalculateChrominanceSize(
int width,
int height,
YCbCrSubsampleRatio ratio)
SubsampleRatio ratio)
{
switch (ratio)
{
case YCbCrSubsampleRatio.YCbCrSubsampleRatio422:
case SubsampleRatio.Ratio422:
return new Size((width + 1) / 2, height);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio420:
case SubsampleRatio.Ratio420:
return new Size((width + 1) / 2, (height + 1) / 2);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio440:
case SubsampleRatio.Ratio440:
return new Size(width, (height + 1) / 2);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio411:
case SubsampleRatio.Ratio411:
return new Size((width + 3) / 4, height);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio410:
case SubsampleRatio.Ratio410:
return new Size((width + 3) / 4, (height + 1) / 2);
default:
// Default to 4:4:4 subsampling.

30
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs

@ -111,6 +111,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.Temp = new byte[2 * Block8x8F.Size];
}
/// <summary>
/// Gets the <see cref="GolangPort.Components.Decoder.SubsampleRatio"/> ratio.
/// </summary>
public SubsampleRatio SubsampleRatio { get; private set; }
/// <summary>
/// Gets the component array
/// </summary>
@ -780,6 +785,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
return;
}
this.SubsampleRatio = GolangPort.Components.Decoder.SubsampleRatio.Undefined;
if (this.ComponentCount == 1)
{
Buffer2D<byte> buffer = Buffer2D<byte>.CreateClean(8 * this.MCUCountX, 8 * this.MCUCountY);
@ -792,28 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int horizontalRatio = h0 / this.Components[1].HorizontalFactor;
int verticalRatio = v0 / this.Components[1].VerticalFactor;
YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444;
switch ((horizontalRatio << 4) | verticalRatio)
{
case 0x11:
ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444;
break;
case 0x12:
ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440;
break;
case 0x21:
ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422;
break;
case 0x22:
ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420;
break;
case 0x41:
ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411;
break;
case 0x42:
ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410;
break;
}
SubsampleRatio ratio = Subsampling.GetSubsampleRatio(horizontalRatio, verticalRatio);
this.ycbcrImage = new YCbCrImage(8 * h0 * this.MCUCountX, 8 * v0 * this.MCUCountY, ratio);

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

@ -55,11 +55,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
private static IImageDecoder OldJpegDecoder => new OrigJpegDecoder();
private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder();
private static IImageDecoder PdfJsJpegDecoder => new JpegDecoder();
[Fact]
[Fact(Skip = "Doesn't really matter")]
public void ParseStream_BasicPropertiesAreCorrect1_Orig()
{
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, bool useOldDecoder)
where TPixel : struct, IPixel<TPixel>
{
IImageDecoder decoder = useOldDecoder ? OldJpegDecoder : PdfJsJpegDecoder;
IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder;
using (Image<TPixel> image = provider.GetImage(decoder))
{
image.DebugSave(provider);
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void DecodeBaselineJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(OldJpegDecoder))
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
{
image.DebugSave(provider);
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void DecodeProgressiveJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(OldJpegDecoder))
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
{
image.DebugSave(provider);
@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine(provider.SourceFileOrDescription);
provider.Utility.TestName = testName;
using (Image<TPixel> image = provider.GetImage(OldJpegDecoder))
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
{
double d = this.GetDifferenceInPercents(image, provider);
this.Output.WriteLine($"Difference using ORIGINAL decoder: {d:0.0000}%");
@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 75)]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 100)]
[WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 100)]
public void DecodeGenerated<TPixel>(
public void DecodeGenerated_Orig<TPixel>(
TestImageProvider<TPixel> provider,
JpegSubsample subsample,
int quality)
@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
var mirror = Image.Load<TPixel>(data);
var mirror = Image.Load<TPixel>(data, OrigJpegDecoder);
mirror.DebugSave(provider, $"_{subsample}_Q{quality}");
}

32
tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs

@ -19,19 +19,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
[Theory]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4, 1)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2, 1)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1, 1)]
[InlineData(SubsampleRatio.Ratio410, 4, 2)]
[InlineData(SubsampleRatio.Ratio411, 4, 1)]
[InlineData(SubsampleRatio.Ratio420, 2, 2)]
[InlineData(SubsampleRatio.Ratio422, 2, 1)]
[InlineData(SubsampleRatio.Ratio440, 1, 2)]
[InlineData(SubsampleRatio.Ratio444, 1, 1)]
internal void CalculateChrominanceSize(
YCbCrImage.YCbCrSubsampleRatio ratioValue,
SubsampleRatio ratio,
int expectedDivX,
int expectedDivY)
{
YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue;
//this.Output.WriteLine($"RATIO: {ratio}");
Size size = YCbCrImage.CalculateChrominanceSize(400, 400, ratio);
//this.Output.WriteLine($"Ch Size: {size}");
@ -40,16 +38,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Theory]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1)]
internal void Create(YCbCrImage.YCbCrSubsampleRatio ratioValue, int expectedCStrideDiv)
[InlineData(SubsampleRatio.Ratio410, 4)]
[InlineData(SubsampleRatio.Ratio411, 4)]
[InlineData(SubsampleRatio.Ratio420, 2)]
[InlineData(SubsampleRatio.Ratio422, 2)]
[InlineData(SubsampleRatio.Ratio440, 1)]
[InlineData(SubsampleRatio.Ratio444, 1)]
internal void Create(SubsampleRatio ratio, int expectedCStrideDiv)
{
YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue;
this.Output.WriteLine($"RATIO: {ratio}");
YCbCrImage img = new YCbCrImage(400, 400, ratio);

Loading…
Cancel
Save