diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index 65f7abfe52..4c1b4f4d1e 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) + internal void NormalizeColorsInto(ref Block8x8F d) { d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); @@ -117,5 +117,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); } - } + + + /// + /// Level shift by +128, clip to [0, 255] + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void NormalizeColorsInplace() + { + this.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); + this.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); + this.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); + this.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); + this.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); + this.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); + this.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); + this.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); + this.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); + this.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); + this.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); + this.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); + this.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); + this.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); + this.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); + this.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); + } + } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 71a1e001c5..4a44d0006e 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public void CopyTo(BufferArea area) { ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref area.DangerousGetPinnableReference()); + ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); int destStride = area.Stride * sizeof(float); CopyRowImpl(ref selfBase, ref destBase, destStride, 0); @@ -446,7 +446,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void CopyColorsTo(Span destinationBuffer, int stride, Block8x8F* tempBlockPtr) { - this.TransformByteConvetibleColorValuesInto(ref *tempBlockPtr); + this.NormalizeColorsInto(ref *tempBlockPtr); ref byte d = ref destinationBuffer.DangerousGetPinnableReference(); float* src = (float*)tempBlockPtr; for (int i = 0; i < 8; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs deleted file mode 100644 index 93e6a6705f..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs +++ /dev/null @@ -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 ColorBuffer { get; } - - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs index 55bd5fe304..7161218815 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs @@ -32,6 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// int VerticalSamplingFactor { get; } + /// + /// Gets the index of the quantization table for this block. + /// + int QuantizationTableIndex { get; } + /// /// Gets the storing the "raw" frequency-domain decoded blocks. /// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks. diff --git a/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs index 7b3318f567..90540384e4 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs @@ -5,12 +5,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common { internal interface IRawJpegData { - Size ImageSize { get; } + Size ImageSizeInPixels { get; } Size ImageSizeInBlocks { get; } int ComponentCount { get; } IEnumerable Components { get; } + + /// + /// Gets the quantization tables, in zigzag order. + /// + Block8x8F[] QuantizationTables { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs new file mode 100644 index 0000000000..bbad6b5776 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs @@ -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(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 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 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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs new file mode 100644 index 0000000000..3953e5616d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs @@ -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(Image destination) + where TPixel : struct, IPixel + { + 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(Image destination) + where TPixel : struct, IPixel + { + while (this.DoPostProcessorStep(destination)) + { + } + } + + private void ConvertColors(Image destination) + where TPixel : struct, IPixel + { + 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 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(ref rgbaVector) = rgb.Vector; + + destRow[x].PackFromVector4(rgbaVector); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs index 95ac196d42..a3f9e4938b 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs @@ -8,6 +8,8 @@ using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { + using System.Runtime.CompilerServices; + /// /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// @@ -23,20 +25,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Pointers to elements of /// private DataPointers pointers; - - /// - /// The component index. - /// - private int componentIndex; - + /// /// Initialize the instance on the stack. /// /// The instance - /// The current component index - public static void Init(JpegBlockPostProcessor* postProcessor, int componentIndex) + public static void Init(JpegBlockPostProcessor* postProcessor) { - postProcessor->componentIndex = componentIndex; postProcessor->data = ComputationData.Create(); postProcessor->pointers = new DataPointers(&postProcessor->data); } @@ -45,10 +40,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Dequantize, perform the inverse DCT and store the blocks to the into the corresponding instances. /// /// The instance - public void ProcessAllBlocks(OrigJpegDecoderCore decoder) + /// The component + public void ProcessAllBlocks(OrigJpegDecoderCore decoder, IJpegComponent component) { - OrigComponent component = decoder.Components[this.componentIndex]; - for (int by = 0; by < component.HeightInBlocks; by++) { for (int bx = 0; bx < component.WidthInBlocks; bx++) @@ -58,6 +52,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } } + public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock) + { + this.data.SourceBlock = sourceBlock.AsFloatBlock(); + int qtIndex = component.QuantizationTableIndex; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.SourceBlock; + + Block8x8F.QuantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + + FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.ResultBlock, ref this.data.TempBlock); + } + + public void ProcessBlockColorsInto( + IRawJpegData decoder, + IJpegComponent component, + ref Block8x8 sourceBlock, + BufferArea destArea) + { + this.QuantizeAndTransform(decoder, component, ref sourceBlock); + + this.data.ResultBlock.NormalizeColorsInplace(); + this.data.ResultBlock.CopyTo(destArea); + } + /// /// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding instance. /// @@ -67,23 +86,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The y index of the block in private void ProcessBlockColors(OrigJpegDecoderCore decoder, IJpegComponent component, int bx, int by) { - ref Block8x8 sourceBlock = ref component.GetBlockReference(bx, by); + ref Block8x8 sourceBlock = ref component.GetBlockReference(bx, @by); - this.data.Block = sourceBlock.AsFloatBlock(); - int qtIndex = decoder.Components[this.componentIndex].Selector; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - Block8x8F* b = this.pointers.Block; - - Block8x8F.QuantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig); - - FastFloatingPointDCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); + this.QuantizeAndTransform(decoder, component, ref sourceBlock); - OrigJpegPixelArea destChannel = decoder.GetDestinationChannel(this.componentIndex); + OrigJpegPixelArea destChannel = decoder.GetDestinationChannel(component.Index); OrigJpegPixelArea destArea = destChannel.GetOffsetedSubAreaForBlock(bx, by); - destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); + destArea.LoadColorsFrom(this.pointers.ResultBlock, this.pointers.TempBlock); } + /// /// Holds the "large" data blocks needed for computations. /// @@ -93,17 +105,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Temporal block 1 to store intermediate and/or final computation results /// - public Block8x8F Block; + public Block8x8F SourceBlock; /// /// Temporal block 1 to store intermediate and/or final computation results /// - public Block8x8F Temp1; + public Block8x8F ResultBlock; /// /// Temporal block 2 to store intermediate and/or final computation results /// - public Block8x8F Temp2; + public Block8x8F TempBlock; /// /// The quantization table as @@ -133,19 +145,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public struct DataPointers { /// - /// Pointer to + /// Pointer to /// - public Block8x8F* Block; + public Block8x8F* SourceBlock; /// - /// Pointer to + /// Pointer to /// - public Block8x8F* Temp1; + public Block8x8F* ResultBlock; /// - /// Pointer to + /// Pointer to /// - public Block8x8F* Temp2; + public Block8x8F* TempBlock; /// /// Pointer to @@ -163,9 +175,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Pointer to internal DataPointers(ComputationData* dataPtr) { - this.Block = &dataPtr->Block; - this.Temp1 = &dataPtr->Temp1; - this.Temp2 = &dataPtr->Temp2; + this.SourceBlock = &dataPtr->SourceBlock; + this.ResultBlock = &dataPtr->ResultBlock; + this.TempBlock = &dataPtr->TempBlock; this.QuantiazationTable = &dataPtr->QuantiazationTable; this.Unzig = dataPtr->Unzig.Data; } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs index 7f0037cb0b..3b5265cfc4 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs @@ -33,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// public int VerticalSamplingFactor { get; private set; } - /// - /// Gets the quantization table destination selector. - /// - public byte Selector { get; private set; } + /// + public int QuantizationTableIndex { get; private set; } /// /// @@ -52,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// public int HeightInBlocks { get; private set; } - + /// /// Initializes /// @@ -82,8 +80,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } } - this.Selector = decoder.Temp[8 + (3 * i)]; - if (this.Selector > OrigJpegDecoderCore.MaxTq) + this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)]; + if (this.QuantizationTableIndex > OrigJpegDecoderCore.MaxTq) { throw new ImageFormatException("Bad Tq value"); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index ad5141c3a1..0643a11300 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -130,9 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// public OrigHuffmanTree[] HuffmanTrees { get; } - /// - /// Gets the quantization tables, in zigzag order. - /// + /// public Block8x8F[] QuantizationTables { get; } /// @@ -141,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// public byte[] Temp { get; } - public Size ImageSize { get; private set; } + public Size ImageSizeInPixels { get; private set; } public Size ImageSizeInBlocks { get; private set; } @@ -155,12 +153,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// Gets the image height /// - public int ImageHeight => this.ImageSize.Height; + public int ImageHeight => this.ImageSizeInPixels.Height; /// /// Gets the image width /// - public int ImageWidth => this.ImageSize.Width; + public int ImageWidth => this.ImageSizeInPixels.Width; /// /// Gets the input stream. @@ -463,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// private void ProcessStartOfScan(int remaining) { - OrigJpegScanDecoder scan = default(OrigJpegScanDecoder); + var scan = default(OrigJpegScanDecoder); OrigJpegScanDecoder.InitStreamReading(&scan, this, remaining); this.InputProcessor.Bits = default(Bits); scan.DecodeBlocks(this); @@ -483,9 +481,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.ComponentCount, componentIndex => { - JpegBlockPostProcessor postProcessor = default(JpegBlockPostProcessor); - JpegBlockPostProcessor.Init(&postProcessor, componentIndex); - postProcessor.ProcessAllBlocks(this); + var postProcessor = default(JpegBlockPostProcessor); + JpegBlockPostProcessor.Init(&postProcessor); + IJpegComponent component = this.Components[componentIndex]; + postProcessor.ProcessAllBlocks(this, component); }); } @@ -1212,9 +1211,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private void InitSizes(int width, int height) { - this.ImageSize = new Size(width, height); + this.ImageSizeInPixels = new Size(width, height); - var sizeInBlocks = (Vector2)(SizeF)this.ImageSize; + var sizeInBlocks = (Vector2)(SizeF)this.ImageSizeInPixels; sizeInBlocks /= 8; sizeInBlocks.X = MathF.Ceiling(sizeInBlocks.X); sizeInBlocks.Y = MathF.Ceiling(sizeInBlocks.Y); diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index cd1e6c7a99..7b8191458c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -3,12 +3,11 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { - using SixLabors.ImageSharp.Formats.Jpeg.Common; - /// /// Represents a single frame component /// @@ -16,13 +15,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { #pragma warning disable SA1401 // Fields should be private - public PdfJsFrameComponent(PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationIdentifier, int index) + public PdfJsFrameComponent(PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) { this.Frame = frame; this.Id = id; this.HorizontalSamplingFactor = horizontalFactor; this.VerticalSamplingFactor = verticalFactor; - this.QuantizationIdentifier = quantizationIdentifier; + this.QuantizationTableIndex = quantizationTableIndex; this.Index = index; } @@ -44,10 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components Buffer2D IJpegComponent.SpectralBlocks => throw new NotImplementedException(); - /// - /// Gets the identifier - /// - public byte QuantizationIdentifier { get; } + /// + public int QuantizationTableIndex { get; } /// /// Gets the block data diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs index e705073fab..eb1c8e0864 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs @@ -815,7 +815,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort using (var computationBuffer = Buffer.CreateClean(64)) using (var multiplicationBuffer = Buffer.CreateClean(64)) { - Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationIdentifier); + Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex); Span computationBufferSpan = computationBuffer; // For AA&N IDCT method, multiplier are equal to quantization diff --git a/src/ImageSharp/Memory/Buffer2D.cs b/src/ImageSharp/Memory/Buffer2D.cs index 8c7b104cf1..620c32bfcf 100644 --- a/src/ImageSharp/Memory/Buffer2D.cs +++ b/src/ImageSharp/Memory/Buffer2D.cs @@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Memory internal class Buffer2D : Buffer, IBuffer2D where T : struct { + public Buffer2D(Size size) + : this(size.Width, size.Height) + { + } + /// /// Initializes a new instance of the class. /// diff --git a/src/ImageSharp/Memory/BufferArea.cs b/src/ImageSharp/Memory/BufferArea.cs index 12843e2092..67dddd77cc 100644 --- a/src/ImageSharp/Memory/BufferArea.cs +++ b/src/ImageSharp/Memory/BufferArea.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Memory public ref T this[int x, int y] => ref this.DestinationBuffer.Span[this.GetIndexOf(x, y)]; - public ref T DangerousGetPinnableReference() => + public ref T GetReferenceToOrigo() => ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index aa224fd709..56921065c7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -263,17 +263,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return result; } - [Fact] - public void TransformByteConvetibleColorValuesInto() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void NormalizeColors(bool inplace) { - Block8x8F block = new Block8x8F(); + var block = default(Block8x8F); float[] input = Create8x8ColorCropTestData(); block.LoadFrom(input); this.Output.WriteLine("Input:"); this.PrintLinearData(input); + + var dest = default(Block8x8F); - Block8x8F dest = new Block8x8F(); - block.TransformByteConvetibleColorValuesInto(ref dest); + if (inplace) + { + dest = block; + dest.NormalizeColorsInplace(); + } + else + { + block.NormalizeColorsInto(ref dest); + } + float[] array = new float[64]; dest.CopyTo(array); @@ -285,6 +297,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + [Theory] [InlineData(1)] [InlineData(2)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs new file mode 100644 index 0000000000..ebed368f85 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -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(TestImageProvider provider) + where TPixel : struct, IPixel + { + string imageFile = provider.SourceFileOrDescription; + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) + using (var pp = new JpegImagePostProcessor(decoder)) + using (var image = new Image(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(TestImageProvider provider) + where TPixel : struct, IPixel + { + string imageFile = provider.SourceFileOrDescription; + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) + using (var pp = new JpegImagePostProcessor(decoder)) + using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) + { + pp.PostProcess(image); + + image.DebugSave(provider); + + ImagingTestCaseUtility testUtil = provider.Utility; + testUtil.TestGroupName = nameof(JpegDecoderTests); + testUtil.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; + + using (Image referenceImage = + provider.GetReferenceOutputImage(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); + } + } + + + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 360ffff211..7784dcb17d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -34,6 +34,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public int VerticalSamplingFactor => throw new NotSupportedException(); + public int QuantizationTableIndex => throw new NotSupportedException(); + public Buffer2D SpectralBlocks { get; private set; } public short MinVal { get; private set; } = short.MaxValue; diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 226e49aecb..58051c894e 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - ref int r = ref area0.DangerousGetPinnableReference(); + ref int r = ref area0.GetReferenceToOrigo(); int expected = buffer[6, 8]; Assert.Equal(expected, r); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index a4c540c5e1..b8d1dbf41f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -22,6 +22,7 @@ public static ImageSimilarityReport Empty => new ImageSimilarityReport(null, null, Enumerable.Empty(), null); + // TODO: This should not be a nullable value! public float? TotalNormalizedDifference { get; } public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue