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