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