mirror of https://github.com/SixLabors/ImageSharp
19 changed files with 409 additions and 117 deletions
@ -1,41 +0,0 @@ |
|||||
using System; |
|
||||
using System.Linq; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Common |
|
||||
{ |
|
||||
internal class JpegPostProcessor |
|
||||
{ |
|
||||
private ComponentPostProcessor[] componentProcessors; |
|
||||
|
|
||||
public JpegPostProcessor(IRawJpegData data) |
|
||||
{ |
|
||||
this.Data = data; |
|
||||
this.componentProcessors = data.Components.Select(c => new ComponentPostProcessor(this, c)).ToArray(); |
|
||||
} |
|
||||
|
|
||||
public IRawJpegData Data { get; } |
|
||||
} |
|
||||
|
|
||||
internal class ComponentPostProcessor : IDisposable |
|
||||
{ |
|
||||
public ComponentPostProcessor(JpegPostProcessor jpegPostProcessor, IJpegComponent component) |
|
||||
{ |
|
||||
this.Component = component; |
|
||||
this.JpegPostProcessor = jpegPostProcessor; |
|
||||
} |
|
||||
|
|
||||
public JpegPostProcessor JpegPostProcessor { get; } |
|
||||
|
|
||||
public IJpegComponent Component { get; } |
|
||||
|
|
||||
public int NumberOfRowGroupSteps { get; } |
|
||||
|
|
||||
public Buffer2D<float> ColorBuffer { get; } |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,75 @@ |
|||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing |
||||
|
{ |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
internal class JpegComponentPostProcessor : IDisposable |
||||
|
{ |
||||
|
private int currentComponentRowInBlocks; |
||||
|
|
||||
|
private readonly Size blockAreaSize; |
||||
|
|
||||
|
public JpegComponentPostProcessor(JpegImagePostProcessor imagePostProcessor, IJpegComponent component) |
||||
|
{ |
||||
|
this.Component = component; |
||||
|
this.ImagePostProcessor = imagePostProcessor; |
||||
|
this.ColorBuffer = new Buffer2D<float>(imagePostProcessor.PostProcessorBufferSize); |
||||
|
|
||||
|
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.VerticalSamplingFactor; |
||||
|
this.blockAreaSize = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor) * 8; |
||||
|
} |
||||
|
|
||||
|
public JpegImagePostProcessor ImagePostProcessor { get; } |
||||
|
|
||||
|
public IJpegComponent Component { get; } |
||||
|
|
||||
|
public Buffer2D<float> ColorBuffer { get; } |
||||
|
|
||||
|
public int BlocksPerRow => this.Component.WidthInBlocks; |
||||
|
|
||||
|
public int BlockRowsPerStep { get; } |
||||
|
|
||||
|
private int HorizontalSamplingFactor => this.Component.HorizontalSamplingFactor; |
||||
|
|
||||
|
private int VerticalSamplingFactor => this.Component.VerticalSamplingFactor; |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.ColorBuffer.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public unsafe void CopyBlocksToColorBuffer() |
||||
|
{ |
||||
|
var blockPp = default(JpegBlockPostProcessor); |
||||
|
JpegBlockPostProcessor.Init(&blockPp); |
||||
|
|
||||
|
for (int y = 0; y < this.BlockRowsPerStep; y++) |
||||
|
{ |
||||
|
int yBlock = this.currentComponentRowInBlocks + y; |
||||
|
int yBuffer = y * this.blockAreaSize.Height; |
||||
|
|
||||
|
for (int x = 0; x < this.BlocksPerRow; x++) |
||||
|
{ |
||||
|
int xBlock = x; |
||||
|
int xBuffer = x * this.blockAreaSize.Width; |
||||
|
|
||||
|
ref Block8x8 block = ref this.Component.GetBlockReference(xBlock, yBlock); |
||||
|
|
||||
|
BufferArea<float> destArea = this.ColorBuffer.GetArea( |
||||
|
xBuffer, |
||||
|
yBuffer, |
||||
|
this.blockAreaSize.Width, |
||||
|
this.blockAreaSize.Height |
||||
|
); |
||||
|
|
||||
|
blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.currentComponentRowInBlocks += this.BlockRowsPerStep; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.ColorSpaces; |
||||
|
using SixLabors.ImageSharp.ColorSpaces.Conversion; |
||||
|
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing |
||||
|
{ |
||||
|
internal class JpegImagePostProcessor : IDisposable |
||||
|
{ |
||||
|
public const int BlockRowsPerStep = 4; |
||||
|
|
||||
|
public const int PixelRowsPerStep = 4 * 8; |
||||
|
|
||||
|
private JpegComponentPostProcessor[] componentProcessors; |
||||
|
|
||||
|
public JpegImagePostProcessor(IRawJpegData rawJpeg) |
||||
|
{ |
||||
|
this.RawJpeg = rawJpeg; |
||||
|
this.NumberOfPostProcessorSteps = rawJpeg.ImageSizeInBlocks.Height / BlockRowsPerStep; |
||||
|
this.PostProcessorBufferSize = new Size(rawJpeg.ImageSizeInBlocks.Width * 8, PixelRowsPerStep); |
||||
|
|
||||
|
this.componentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); |
||||
|
} |
||||
|
|
||||
|
public IRawJpegData RawJpeg { get; } |
||||
|
|
||||
|
public int NumberOfPostProcessorSteps { get; } |
||||
|
|
||||
|
public Size PostProcessorBufferSize { get; } |
||||
|
|
||||
|
public int CurrentImageRowInPixels { get; private set; } |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
foreach (JpegComponentPostProcessor cpp in this.componentProcessors) |
||||
|
{ |
||||
|
cpp.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool DoPostProcessorStep<TPixel>(Image<TPixel> destination) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
if (this.RawJpeg.ComponentCount != 3) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
foreach (JpegComponentPostProcessor cpp in this.componentProcessors) |
||||
|
{ |
||||
|
cpp.CopyBlocksToColorBuffer(); |
||||
|
} |
||||
|
|
||||
|
this.ConvertColors(destination); |
||||
|
|
||||
|
this.CurrentImageRowInPixels += PixelRowsPerStep; |
||||
|
return this.CurrentImageRowInPixels < this.RawJpeg.ImageSizeInPixels.Height; |
||||
|
} |
||||
|
|
||||
|
public void PostProcess<TPixel>(Image<TPixel> destination) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
while (this.DoPostProcessorStep(destination)) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ConvertColors<TPixel>(Image<TPixel> destination) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
int maxY = Math.Min(destination.Height, this.CurrentImageRowInPixels + PixelRowsPerStep); |
||||
|
|
||||
|
JpegComponentPostProcessor[] cp = this.componentProcessors; |
||||
|
|
||||
|
YCbCrAndRgbConverter converter = new YCbCrAndRgbConverter(); |
||||
|
|
||||
|
Vector4 rgbaVector = new Vector4(0, 0, 0, 1); |
||||
|
|
||||
|
for (int yy = this.CurrentImageRowInPixels; yy < maxY; yy++) |
||||
|
{ |
||||
|
int y = yy - this.CurrentImageRowInPixels; |
||||
|
|
||||
|
Span<TPixel> destRow = destination.GetRowSpan(yy); |
||||
|
|
||||
|
for (int x = 0; x < destination.Width; x++) |
||||
|
{ |
||||
|
float colY = cp[0].ColorBuffer[x, y]; |
||||
|
float colCb = cp[1].ColorBuffer[x, y]; |
||||
|
float colCr = cp[2].ColorBuffer[x, y]; |
||||
|
|
||||
|
YCbCr yCbCr = new YCbCr(colY, colCb, colCr); |
||||
|
Rgb rgb = converter.Convert(yCbCr); |
||||
|
|
||||
|
Unsafe.As<Vector4, Vector3>(ref rgbaVector) = rgb.Vector; |
||||
|
|
||||
|
destRow[x].PackFromVector4(rgbaVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
||||
|
{ |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
||||
|
|
||||
|
using Xunit; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
public class JpegImagePostProcessorTests |
||||
|
{ |
||||
|
public static string[] BaselineTestJpegs = |
||||
|
{ |
||||
|
TestImages.Jpeg.Baseline.Calliphora, |
||||
|
TestImages.Jpeg.Baseline.Cmyk, |
||||
|
TestImages.Jpeg.Baseline.Ycck, |
||||
|
TestImages.Jpeg.Baseline.Jpeg400, |
||||
|
TestImages.Jpeg.Baseline.Testorig420, |
||||
|
TestImages.Jpeg.Baseline.Jpeg420Small, |
||||
|
TestImages.Jpeg.Baseline.Jpeg444, |
||||
|
TestImages.Jpeg.Baseline.Bad.BadEOF, |
||||
|
TestImages.Jpeg.Baseline.Bad.ExifUndefType, |
||||
|
}; |
||||
|
|
||||
|
public static string[] ProgressiveTestJpegs = |
||||
|
{ |
||||
|
TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, |
||||
|
TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF |
||||
|
}; |
||||
|
|
||||
|
public JpegImagePostProcessorTests(ITestOutputHelper output) |
||||
|
{ |
||||
|
this.Output = output; |
||||
|
} |
||||
|
|
||||
|
private ITestOutputHelper Output { get; } |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] |
||||
|
public void DoProcessorStep<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
string imageFile = provider.SourceFileOrDescription; |
||||
|
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) |
||||
|
using (var pp = new JpegImagePostProcessor(decoder)) |
||||
|
using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight)) |
||||
|
{ |
||||
|
pp.DoPostProcessorStep(image); |
||||
|
|
||||
|
image.DebugSave(provider); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] |
||||
|
public void PostProcess<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
string imageFile = provider.SourceFileOrDescription; |
||||
|
using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) |
||||
|
using (var pp = new JpegImagePostProcessor(decoder)) |
||||
|
using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight)) |
||||
|
{ |
||||
|
pp.PostProcess(image); |
||||
|
|
||||
|
image.DebugSave(provider); |
||||
|
|
||||
|
ImagingTestCaseUtility testUtil = provider.Utility; |
||||
|
testUtil.TestGroupName = nameof(JpegDecoderTests); |
||||
|
testUtil.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; |
||||
|
|
||||
|
using (Image<TPixel> referenceImage = |
||||
|
provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false)) |
||||
|
{ |
||||
|
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); |
||||
|
|
||||
|
this.Output.WriteLine("Difference: "+ report.DifferencePercentageString); |
||||
|
|
||||
|
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
Assert.True(report.TotalNormalizedDifference.Value < 0.005f); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue