Browse Source

reached consistent state with component scaling data

pull/322/head
Anton Firszov 9 years ago
parent
commit
8759f971eb
  1. 24
      src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs
  2. 3
      src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs
  3. 5
      src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs
  4. 31
      src/ImageSharp/Formats/Jpeg/Common/SizeExtensions.cs
  5. 30
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
  6. 6
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  7. 84
      tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
  8. 4
      tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
  9. BIN
      tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump

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

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
@ -11,14 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// </summary>
internal static class ComponentUtils
{
//public static Size SizeInBlocks(this IJpegComponent component) => new Size(component.WidthInBlocks, component.HeightInBlocks);
// In Jpeg these are really useful operations:
public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height);
public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height);
public static ref Block8x8 GetBlockReference(this IJpegComponent component, int bx, int by)
{
return ref component.SpectralBlocks[bx, by];
@ -74,22 +66,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
(int divX, int divY) = ratio.GetChrominanceSubSampling();
var size = new Size(width, height);
return size.GetSubSampledSize(divX, divY);
return size.DivideRoundUp(divX, divY);
}
// TODO: Find a better place for this method
public static Size GetSubSampledSize(this Size originalSize, int divX, int divY)
{
var sizeVect = (Vector2)(SizeF)originalSize;
sizeVect /= new Vector2(divX, divY);
sizeVect.X = MathF.Ceiling(sizeVect.X);
sizeVect.Y = MathF.Ceiling(sizeVect.Y);
return new Size((int)sizeVect.X, (int)sizeVect.Y);
}
public static Size GetSubSampledSize(this Size originalSize, int subsamplingDivisor) =>
GetSubSampledSize(originalSize, subsamplingDivisor, subsamplingDivisor);
// TODO: Not needed by new JpegImagePostprocessor
public static (int divX, int divY) GetChrominanceSubSampling(this SubsampleRatio ratio)

3
src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs

@ -7,9 +7,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
Size ImageSizeInPixels { get; }
// TODO: Kill this
Size ImageSizeInBlocks { get; }
int ComponentCount { get; }
IEnumerable<IJpegComponent> Components { get; }

5
src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs

@ -21,8 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing
public JpegImagePostProcessor(IRawJpegData rawJpeg)
{
this.RawJpeg = rawJpeg;
this.NumberOfPostProcessorSteps = rawJpeg.ImageSizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(rawJpeg.ImageSizeInBlocks.Width * 8, PixelRowsPerStep);
IJpegComponent c0 = rawJpeg.Components.First();
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);
this.componentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray();
}

31
src/ImageSharp/Formats/Jpeg/Common/SizeExtensions.cs

@ -0,0 +1,31 @@
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
/// <summary>
/// Extension methods for <see cref="Size"/>
/// </summary>
internal static class SizeExtensions
{
public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height);
public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height);
public static Size DivideRoundUp(this Size originalSize, int divX, int divY)
{
var sizeVect = (Vector2)(SizeF)originalSize;
sizeVect /= new Vector2(divX, divY);
sizeVect.X = MathF.Ceiling(sizeVect.X);
sizeVect.Y = MathF.Ceiling(sizeVect.Y);
return new Size((int)sizeVect.X, (int)sizeVect.Y);
}
public static Size DivideRoundUp(this Size originalSize, int divisor) =>
DivideRoundUp(originalSize, divisor, divisor);
public static Size DivideRoundUp(this Size originalSize, Size divisor) =>
DivideRoundUp(originalSize, divisor.Width, divisor.Height);
}
}

30
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs

@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public Size SamplingFactors { get; private set; }
public Size SubSamplingDivisors { get; private set; } = new Size(1, 1);
public Size SubSamplingDivisors { get; private set; }
public int HorizontalSamplingFactor => this.SamplingFactors.Width;
@ -57,15 +57,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void InitializeDerivedData(OrigJpegDecoderCore decoder)
{
this.SizeInBlocks = decoder.ImageSizeInBlocks.MultiplyBy(this.SamplingFactors);
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.SizeInBlocks);
if (decoder.ComponentCount > 1 && (this.Index == 1 || this.Index == 2))
// For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
// Theoretically, 4-component JPEG images could mix and match hv values
// but in practice, those two combinations are the only ones in use,
// and it simplifies the applyBlack code below if we can assume that:
// - for CMYK, the C and K channels have full samples, and if the M
// and Y channels subsample, they subsample both horizontally and
// vertically.
// - for YCbCrK, the Y and K channels have full samples.
this.SizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(this.SamplingFactors);
if (this.Index == 0 || this.Index == 3)
{
Size s0 = decoder.Components[0].SamplingFactors;
this.SubSamplingDivisors = s0.DivideBy(this.SamplingFactors);
this.SubSamplingDivisors = new Size(1, 1);
}
else
{
OrigComponent c0 = decoder.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.SizeInBlocks);
}
/// <summary>

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

@ -139,8 +139,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public Size ImageSizeInPixels { get; private set; }
public Size ImageSizeInBlocks { get; private set; }
public Size ImageSizeInMCU { get; private set; }
/// <summary>
@ -1180,7 +1178,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.ImageSizeInPixels = new Size(width, height);
if (this.Temp[5] != this.ComponentCount)
{
throw new ImageFormatException("SOF has wrong length");
@ -1199,14 +1196,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int h0 = this.Components[0].HorizontalSamplingFactor;
int v0 = this.Components[0].VerticalSamplingFactor;
this.ImageSizeInMCU = this.ImageSizeInPixels.GetSubSampledSize(8 * h0, 8 * v0);
this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0);
foreach (OrigComponent component in this.Components)
{
component.InitializeDerivedData(this);
}
this.ImageSizeInBlocks = this.Components[0].SizeInBlocks;
this.SubsampleRatio = ComponentUtils.GetSubsampleRatio(this.Components);
}
}

84
tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs

@ -8,7 +8,9 @@ using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
{
using System.Text;
public class ParseStreamTests
{
private ITestOutputHelper Output { get; }
@ -21,39 +23,68 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact]
public void ComponentScalingIsCorrect_1ChannelJpeg()
{
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400))
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400, true))
{
Assert.Equal(1, decoder.ComponentCount);
Assert.Equal(1, decoder.Components.Length);
Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8);
Size sizeInBlocks = decoder.ImageSizeInBlocks;
Size expectedSizeInBlocks = decoder.ImageSizeInPixels.GetSubSampledSize(8);
Assert.Equal(expectedSizeInBlocks, sizeInBlocks);
Assert.Equal(sizeInBlocks, decoder.ImageSizeInMCU);
Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU);
var uniform1 = new Size(1, 1);
OrigComponent c0 = decoder.Components[0];
VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1);
}
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, 3, 1, 1)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif, 3, 2, 2)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 3, 2, 2)]
[InlineData(TestImages.Jpeg.Baseline.Ycck, 4, 1, 1)] // TODO: Find Ycck or Cmyk images with different subsampling
[InlineData(TestImages.Jpeg.Baseline.Cmyk, 4, 1, 1)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)]
[InlineData(TestImages.Jpeg.Baseline.Testorig420)]
[InlineData(TestImages.Jpeg.Baseline.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk)]
public void PrintComponentData(string imageFile)
{
StringBuilder bld = new StringBuilder();
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true))
{
bld.AppendLine(imageFile);
bld.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}");
OrigComponent c0 = decoder.Components[0];
OrigComponent c1 = decoder.Components[1];
bld.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}");
bld.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}");
}
this.Output.WriteLine(bld.ToString());
}
public static readonly TheoryData<string, int, object, object> ComponentVerificationData = new TheoryData<string, int, object, object>()
{
{ TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) },
// TODO: Find Ycck or Cmyk images with different subsampling
{ TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) },
};
[Theory]
[MemberData(nameof(ComponentVerificationData))]
public void ComponentScalingIsCorrect_MultiChannelJpeg(
string imageFile,
int componentCount,
int hDiv,
int vDiv)
object expectedLumaFactors,
object expectedChromaFactors)
{
Size divisor = new Size(hDiv, vDiv);
Size fLuma = (Size)expectedLumaFactors;
Size fChroma = (Size)expectedChromaFactors;
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile))
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true))
{
Assert.Equal(componentCount, decoder.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
@ -63,20 +94,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
OrigComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1);
Size expectedLumaSizeInBlocks = decoder.ImageSizeInPixels.GetSubSampledSize(8);
Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideBy(divisor);
Size expectedLumaSamplingFactors = expectedLumaSizeInBlocks.DivideBy(decoder.ImageSizeInMCU);
Size expectedChromaSamplingFactors = expectedLumaSamplingFactors.DivideBy(divisor);
Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma) ;
VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, expectedLumaSamplingFactors, uniform1);
VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, expectedChromaSamplingFactors, divisor);
VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, expectedChromaSamplingFactors, divisor);
Size divisor = fLuma.DivideBy(fChroma);
Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor);
VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1);
VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor);
VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor);
if (componentCount == 4)
{
OrigComponent c3 = decoder.Components[2];
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, expectedLumaSamplingFactors, uniform1);
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1);
}
}
}

4
tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs

@ -173,13 +173,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
Assert.False(failed);
}
internal static OrigJpegDecoderCore ParseStream(string testFileName)
internal static OrigJpegDecoderCore ParseStream(string testFileName, bool metaDataOnly = false)
{
byte[] bytes = TestFile.Create(testFileName).Bytes;
using (var ms = new MemoryStream(bytes))
{
var decoder = new OrigJpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms);
decoder.ParseStream(ms, metaDataOnly);
return decoder;
}
}

BIN
tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump

Binary file not shown.
Loading…
Cancel
Save