Browse Source

Merge branch 'jpeg-lab' of https://github.com/SixLabors/ImageSharp into jpeg-lab

af/merge-core
James Jackson-South 9 years ago
parent
commit
68cc3ede9e
  1. 2
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  2. 4
      src/ImageSharp/Formats/Jpeg/Common/Decoder/IJpegComponent.cs
  3. 99
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs
  4. 13
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs

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

@ -424,7 +424,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <param name="qtPtr">Qt pointer</param>
/// <param name="unzigPtr">Unzig pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void QuantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;

4
src/ImageSharp/Formats/Jpeg/Common/Decoder/IJpegComponent.cs

@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
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.
/// Gets the <see cref="Buffer2D{Block8x8}"/> storing the "raw" frequency-domain decoded + unzigged blocks.
/// We need to apply IDCT and dequantiazition to transform them into color-space blocks.
/// </summary>
Buffer2D<Block8x8> SpectralBlocks { get; }
}

99
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs

@ -1,26 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Encapsulates the execution post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
/// (1) Dequantization <br/>
/// (2) IDCT <br/>
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageBase{TPixel}.Height"/>).
/// </summary>
internal class JpegImagePostProcessor : IDisposable
{
/// <summary>
/// The number of block rows to be processed in one Step.
/// </summary>
public const int BlockRowsPerStep = 4;
/// <summary>
/// The number of image pixel rows to be processed in one step.
/// </summary>
public const int PixelRowsPerStep = 4 * 8;
/// <summary>
/// Temporal buffer to store a row of colors.
/// </summary>
private readonly Buffer<Vector4> rgbaBuffer;
/// <summary>
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
/// </summary>
private JpegColorConverter colorConverter;
/// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
/// </summary>
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
public JpegImagePostProcessor(IRawJpegData rawJpeg)
{
this.RawJpeg = rawJpeg;
@ -33,16 +55,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
}
/// <summary>
/// Gets the <see cref="JpegComponentPostProcessor"/> instances.
/// </summary>
public JpegComponentPostProcessor[] ComponentProcessors { get; }
/// <summary>
/// Gets the <see cref="IRawJpegData"/> to be processed.
/// </summary>
public IRawJpegData RawJpeg { get; }
/// <summary>
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="PixelRowsPerStep"/>.
/// </summary>
public int NumberOfPostProcessorSteps { get; }
/// <summary>
/// Gets the size of the temporal buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// </summary>
public Size PostProcessorBufferSize { get; }
public int CurrentImageRowInPixels { get; private set; }
/// <summary>
/// Gets the value of the counter that grows by each step by <see cref="PixelRowsPerStep"/>.
/// </summary>
public int PixelRowCounter { get; private set; }
/// <inheritdoc />
public void Dispose()
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
@ -53,43 +91,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.rgbaBuffer.Dispose();
}
public void DoPostProcessorStep<TPixel>(Image<TPixel> destination)
/// <summary>
/// Process all pixels into 'destination'. The image dimensions should match <see cref="RawJpeg"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
public void PostProcess<TPixel>(Image<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
this.PixelRowCounter = 0;
if (this.RawJpeg.ImageSizeInPixels != destination.Size())
{
cpp.CopyBlocksToColorBuffer();
throw new ArgumentException("Input image is not of the size of the processed one!");
}
this.ConvertColors(destination);
this.CurrentImageRowInPixels += PixelRowsPerStep;
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height)
{
this.DoPostProcessorStep(destination);
}
}
public void PostProcess<TPixel>(Image<TPixel> destination)
/// <summary>
/// Execute one step rocessing <see cref="PixelRowsPerStep"/> pixel rows into 'destination'.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image.</param>
public void DoPostProcessorStep<TPixel>(Image<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
{
if (this.RawJpeg.ImageSizeInPixels != destination.Size())
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
throw new ArgumentException("Input image is not of the size of the processed one!");
cpp.CopyBlocksToColorBuffer();
}
while (this.CurrentImageRowInPixels < this.RawJpeg.ImageSizeInPixels.Height)
{
this.DoPostProcessorStep(destination);
}
this.ConvertColorsInto(destination);
this.PixelRowCounter += PixelRowsPerStep;
}
private void ConvertColors<TPixel>(Image<TPixel> destination)
/// <summary>
/// Convert and copy <see cref="PixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
private void ConvertColorsInto<TPixel>(Image<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
{
int maxY = Math.Min(destination.Height, this.CurrentImageRowInPixels + PixelRowsPerStep);
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);
Buffer2D<float>[] buffers = this.ComponentProcessors.Select(cp => cp.ColorBuffer).ToArray();
for (int yy = this.CurrentImageRowInPixels; yy < maxY; yy++)
for (int yy = this.PixelRowCounter; yy < maxY; yy++)
{
int y = yy - this.CurrentImageRowInPixels;
int y = yy - this.PixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer);

13
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs

@ -1,18 +1,15 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Memory;
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.Primitives;
/// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="Buffer{T}"/>-s into Jpeg image channels.
/// </summary>
@ -63,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
Block8x8F* b = this.pointers.SourceBlock;
Block8x8F.QuantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig);
Block8x8F.DequantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig);
FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2);
}
@ -77,13 +74,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.QuantizeAndTransform(decoder, component, ref sourceBlock);
this.data.WorkspaceBlock1.NormalizeColorsInplace();
Size divs = component.SubSamplingDivisors;
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// Unfortunately, we need to emulate this to be "more accurate" :(
this.data.WorkspaceBlock1.RoundInplace();
Size divs = component.SubSamplingDivisors;
this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height);
}

Loading…
Cancel
Save