Browse Source

JpegComponentPostProcessor works for Calliphora.jpg!

pull/322/head
Anton Firszov 9 years ago
parent
commit
c9eaff286e
  1. 29
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs
  2. 4
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  3. 41
      src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs
  4. 5
      src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs
  5. 7
      src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs
  6. 75
      src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegComponentPostProcessor.cs
  7. 106
      src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs
  8. 84
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs
  9. 12
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
  10. 23
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  11. 13
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
  12. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs
  13. 5
      src/ImageSharp/Memory/Buffer2D.cs
  14. 2
      src/ImageSharp/Memory/BufferArea.cs
  15. 23
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  16. 90
      tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
  17. 2
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs
  18. 2
      tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
  19. 1
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs

29
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs

@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// </summary>
/// <param name="d">The destination block</param>
[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);
}
}
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// </summary>
[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);
}
}
}

4
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs

@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
public void CopyTo(BufferArea<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.DangerousGetPinnableReference());
ref byte destBase = ref Unsafe.As<float, byte>(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<byte> 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++)

41
src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs

@ -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()
{
}
}
}

5
src/ImageSharp/Formats/Jpeg/Common/IJpegComponent.cs

@ -32,6 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// </summary>
int VerticalSamplingFactor { get; }
/// <summary>
/// Gets the index of the quantization table for this block.
/// </summary>
int QuantizationTableIndex { get; }
/// <summary>
/// Gets the <see cref="Buffer2D{Block8x8}"/> storing the "raw" frequency-domain decoded blocks.
/// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks.

7
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<IJpegComponent> Components { get; }
/// <summary>
/// Gets the quantization tables, in zigzag order.
/// </summary>
Block8x8F[] QuantizationTables { get; }
}
}

75
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<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;
}
}
}

106
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<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);
}
}
}
}
}

84
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;
/// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="Buffer{T}"/>-s into Jpeg image channels.
/// </summary>
@ -23,20 +25,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Pointers to elements of <see cref="data"/>
/// </summary>
private DataPointers pointers;
/// <summary>
/// The component index.
/// </summary>
private int componentIndex;
/// <summary>
/// Initialize the <see cref="JpegBlockPostProcessor"/> instance on the stack.
/// </summary>
/// <param name="postProcessor">The <see cref="JpegBlockPostProcessor"/> instance</param>
/// <param name="componentIndex">The current component index</param>
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 <see cref="OrigJpegPixelArea"/> instances.
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void ProcessAllBlocks(OrigJpegDecoderCore decoder)
/// <param name="component">The component</param>
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<float> destArea)
{
this.QuantizeAndTransform(decoder, component, ref sourceBlock);
this.data.ResultBlock.NormalizeColorsInplace();
this.data.ResultBlock.CopyTo(destArea);
}
/// <summary>
/// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding <see cref="OrigJpegPixelArea"/> instance.
/// </summary>
@ -67,23 +86,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="by">The y index of the block in <see cref="OrigComponent.SpectralBlocks"/></param>
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);
}
/// <summary>
/// Holds the "large" data blocks needed for computations.
/// </summary>
@ -93,17 +105,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Temporal block 1 to store intermediate and/or final computation results
/// </summary>
public Block8x8F Block;
public Block8x8F SourceBlock;
/// <summary>
/// Temporal block 1 to store intermediate and/or final computation results
/// </summary>
public Block8x8F Temp1;
public Block8x8F ResultBlock;
/// <summary>
/// Temporal block 2 to store intermediate and/or final computation results
/// </summary>
public Block8x8F Temp2;
public Block8x8F TempBlock;
/// <summary>
/// The quantization table as <see cref="Block8x8F"/>
@ -133,19 +145,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public struct DataPointers
{
/// <summary>
/// Pointer to <see cref="DecodedBlock.Block"/>
/// Pointer to <see cref="ComputationData.SourceBlock"/>
/// </summary>
public Block8x8F* Block;
public Block8x8F* SourceBlock;
/// <summary>
/// Pointer to <see cref="ComputationData.Temp1"/>
/// Pointer to <see cref="ComputationData.ResultBlock"/>
/// </summary>
public Block8x8F* Temp1;
public Block8x8F* ResultBlock;
/// <summary>
/// Pointer to <see cref="ComputationData.Temp2"/>
/// Pointer to <see cref="ComputationData.TempBlock"/>
/// </summary>
public Block8x8F* Temp2;
public Block8x8F* TempBlock;
/// <summary>
/// Pointer to <see cref="ComputationData.QuantiazationTable"/>
@ -163,9 +175,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="dataPtr">Pointer to <see cref="ComputationData"/></param>
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;
}

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

@ -33,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <inheritdoc />
public int VerticalSamplingFactor { get; private set; }
/// <summary>
/// Gets the quantization table destination selector.
/// </summary>
public byte Selector { get; private set; }
/// <inheritdoc />
public int QuantizationTableIndex { get; private set; }
/// <inheritdoc />
/// <summary>
@ -52,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <inheritdoc />
public int HeightInBlocks { get; private set; }
/// <summary>
/// Initializes <see cref="SpectralBlocks"/>
/// </summary>
@ -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");
}

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

@ -130,9 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
public OrigHuffmanTree[] HuffmanTrees { get; }
/// <summary>
/// Gets the quantization tables, in zigzag order.
/// </summary>
/// <inheritdoc />
public Block8x8F[] QuantizationTables { get; }
/// <summary>
@ -141,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
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
/// <summary>
/// Gets the image height
/// </summary>
public int ImageHeight => this.ImageSize.Height;
public int ImageHeight => this.ImageSizeInPixels.Height;
/// <summary>
/// Gets the image width
/// </summary>
public int ImageWidth => this.ImageSize.Width;
public int ImageWidth => this.ImageSizeInPixels.Width;
/// <summary>
/// Gets the input stream.
@ -463,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </exception>
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);

13
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;
/// <summary>
/// Represents a single frame component
/// </summary>
@ -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<Block8x8> IJpegComponent.SpectralBlocks => throw new NotImplementedException();
/// <summary>
/// Gets the identifier
/// </summary>
public byte QuantizationIdentifier { get; }
/// <inheritdoc />
public int QuantizationTableIndex { get; }
/// <summary>
/// Gets the block data

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs

@ -815,7 +815,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
using (var computationBuffer = Buffer<short>.CreateClean(64))
using (var multiplicationBuffer = Buffer<short>.CreateClean(64))
{
Span<short> quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationIdentifier);
Span<short> quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex);
Span<short> computationBufferSpan = computationBuffer;
// For AA&N IDCT method, multiplier are equal to quantization

5
src/ImageSharp/Memory/Buffer2D.cs

@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Memory
internal class Buffer2D<T> : Buffer<T>, IBuffer2D<T>
where T : struct
{
public Buffer2D(Size size)
: this(size.Width, size.Height)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>

2
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)]

23
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)]

90
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<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);
}
}
}
}
}

2
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<Block8x8> SpectralBlocks { get; private set; }
public short MinVal { get; private set; } = short.MaxValue;

2
tests/ImageSharp.Tests/Memory/BufferAreaTests.cs

@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
BufferArea<int> 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);

1
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs

@ -22,6 +22,7 @@
public static ImageSimilarityReport Empty =>
new ImageSimilarityReport(null, null, Enumerable.Empty<PixelDifference>(), null);
// TODO: This should not be a nullable value!
public float? TotalNormalizedDifference { get; }
public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue

Loading…
Cancel
Save