Browse Source

Merge pull request #210 from FireNero/buffer-192

Refactor JpegDecoder to use Buffers.
af/merge-core
James Jackson-South 9 years ago
committed by GitHub
parent
commit
3e9c39a00c
  1. 55
      src/ImageSharp/Formats/Jpeg/Components/Decoder/DecodedBlockArray.cs
  2. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockProcessor.cs
  3. 62
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegPixelArea.cs
  4. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegScanDecoder.cs
  5. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrImage.cs
  6. 52
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  7. 6
      tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs

55
src/ImageSharp/Formats/Jpeg/Components/Decoder/DecodedBlockArray.cs

@ -1,55 +0,0 @@
// <copyright file="DecodedBlockArray.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;
/// <summary>
/// Because <see cref="System.Array.Length"/> has no information for rented arrays,
/// we need to store the count and the buffer separately when storing pooled <see cref="DecodedBlock"/> arrays.
/// </summary>
internal struct DecodedBlockArray : IDisposable
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data in <see cref="JpegDecoderCore.DecodedBlocks"/>.
/// Should always clean arrays when returning!
/// </summary>
private static readonly ArrayPool<DecodedBlock> ArrayPool = ArrayPool<DecodedBlock>.Create();
/// <summary>
/// Initializes a new instance of the <see cref="DecodedBlockArray"/> struct. Rents a buffer.
/// </summary>
/// <param name="count">The number of valid <see cref="DecodedBlock"/>-s</param>
public DecodedBlockArray(int count)
{
this.Count = count;
this.Buffer = ArrayPool.Rent(count);
}
/// <summary>
/// Gets the number of actual <see cref="DecodedBlock"/>-s inside <see cref="Buffer"/>
/// </summary>
public int Count { get; }
/// <summary>
/// Gets the rented buffer.
/// </summary>
public DecodedBlock[] Buffer { get; private set; }
/// <summary>
/// Returns the rented buffer to the pool.
/// </summary>
public void Dispose()
{
if (this.Buffer != null)
{
ArrayPool.Return(this.Buffer, true);
this.Buffer = null;
}
}
}
}

8
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockProcessor.cs

@ -9,7 +9,7 @@ namespace ImageSharp.Formats.Jpg
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
/// <summary> /// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="DecodedBlockArray"/>-s into Jpeg image channels. /// Encapsulates the implementation of processing "raw" <see cref="Buffer{T}"/>-s into Jpeg image channels.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct JpegBlockProcessor internal unsafe struct JpegBlockProcessor
@ -47,10 +47,10 @@ namespace ImageSharp.Formats.Jpg
/// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param> /// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param>
public void ProcessAllBlocks(JpegDecoderCore decoder) public void ProcessAllBlocks(JpegDecoderCore decoder)
{ {
DecodedBlockArray blockArray = decoder.DecodedBlocks[this.componentIndex]; Buffer<DecodedBlock> blockArray = decoder.DecodedBlocks[this.componentIndex];
for (int i = 0; i < blockArray.Count; i++) for (int i = 0; i < blockArray.Length; i++)
{ {
this.ProcessBlockColors(decoder, ref blockArray.Buffer[i]); this.ProcessBlockColors(decoder, ref blockArray[i]);
} }
} }

62
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegPixelArea.cs

@ -4,7 +4,6 @@
// </copyright> // </copyright>
namespace ImageSharp.Formats.Jpg namespace ImageSharp.Formats.Jpg
{ {
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
@ -15,20 +14,30 @@ namespace ImageSharp.Formats.Jpg
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegPixelArea" /> struct from existing data. /// Initializes a new instance of the <see cref="JpegPixelArea" /> struct from existing data.
/// </summary> /// </summary>
/// <param name="pixels">The pixel array</param> /// <param name="pixels">The pixel buffer</param>
/// <param name="striede">The stride</param> /// <param name="stride">The stride</param>
/// <param name="offset">The offset</param> /// <param name="offset">The offset</param>
public JpegPixelArea(byte[] pixels, int striede, int offset) public JpegPixelArea(Buffer2D<byte> pixels, int stride, int offset)
{ {
this.Stride = striede; this.Stride = stride;
this.Pixels = pixels; this.Pixels = pixels;
this.Offset = offset; this.Offset = offset;
} }
/// <summary> /// <summary>
/// Gets the pixels. /// Initializes a new instance of the <see cref="JpegPixelArea" /> struct from existing buffer.
/// <see cref="Stride"/> will be set to <see cref="Buffer2D{T}.Width"/> of <paramref name="pixels"/> and <see cref="Offset"/> will be set to 0.
/// </summary> /// </summary>
public byte[] Pixels { get; private set; } /// <param name="pixels">The pixel buffer</param>
public JpegPixelArea(Buffer2D<byte> pixels)
: this(pixels, pixels.Width, 0)
{
}
/// <summary>
/// Gets the pixels buffer.
/// </summary>
public Buffer2D<byte> Pixels { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea)) /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea))
@ -36,21 +45,19 @@ namespace ImageSharp.Formats.Jpg
public bool IsInitialized => this.Pixels != null; public bool IsInitialized => this.Pixels != null;
/// <summary> /// <summary>
/// Gets or the stride. /// Gets the stride.
/// </summary> /// </summary>
public int Stride { get; } public int Stride { get; }
/// <summary> /// <summary>
/// Gets or the offset. /// Gets the offset.
/// </summary> /// </summary>
public int Offset { get; } public int Offset { get; }
/// <summary> /// <summary>
/// Gets a <see cref="MutableSpan{T}" /> of bytes to the pixel area /// Gets a <see cref="MutableSpan{T}" /> of bytes to the pixel area
/// </summary> /// </summary>
public MutableSpan<byte> Span => new MutableSpan<byte>(this.Pixels, this.Offset); public MutableSpan<byte> Span => new MutableSpan<byte>(this.Pixels.Array, this.Offset);
private static ArrayPool<byte> BytePool => ArrayPool<byte>.Shared;
/// <summary> /// <summary>
/// Returns the pixel at (x, y) /// Returns the pixel at (x, y)
@ -67,35 +74,6 @@ namespace ImageSharp.Formats.Jpg
} }
} }
/// <summary>
/// Creates a new instance of the <see cref="JpegPixelArea" /> struct.
/// Pixel array will be taken from a pool, this instance will be the owner of it's pixel data, therefore
/// <see cref="ReturnPooled" /> should be called when the instance is no longer needed.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>A <see cref="JpegPixelArea" /> with pooled data</returns>
public static JpegPixelArea CreatePooled(int width, int height)
{
int size = width * height;
byte[] pixels = BytePool.Rent(size);
return new JpegPixelArea(pixels, width, 0);
}
/// <summary>
/// Returns <see cref="Pixels" /> to the pool
/// </summary>
public void ReturnPooled()
{
if (this.Pixels == null)
{
return;
}
BytePool.Return(this.Pixels);
this.Pixels = null;
}
/// <summary> /// <summary>
/// Gets the subarea that belongs to the Block8x8 defined by block indices /// Gets the subarea that belongs to the Block8x8 defined by block indices
/// </summary> /// </summary>
@ -129,7 +107,7 @@ namespace ImageSharp.Formats.Jpg
public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp) public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp)
{ {
// Level shift by +128, clip to [0, 255], and write to dst. // Level shift by +128, clip to [0, 255], and write to dst.
block->CopyColorsTo(new MutableSpan<byte>(this.Pixels, this.Offset), this.Stride, temp); block->CopyColorsTo(new MutableSpan<byte>(this.Pixels.Array, this.Offset), this.Stride, temp);
} }
} }
} }

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegScanDecoder.cs

@ -171,7 +171,7 @@ namespace ImageSharp.Formats.Jpg
// Take an existing block (required when progressive): // Take an existing block (required when progressive):
int blockIndex = this.GetBlockIndex(decoder); int blockIndex = this.GetBlockIndex(decoder);
this.data.Block = decoder.DecodedBlocks[this.ComponentIndex].Buffer[blockIndex].Block; this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block;
if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) if (!decoder.InputProcessor.UnexpectedEndOfStreamReached)
{ {
@ -179,8 +179,8 @@ namespace ImageSharp.Formats.Jpg
} }
// Store the decoded block // Store the decoded block
DecodedBlockArray blocks = decoder.DecodedBlocks[this.ComponentIndex]; Buffer<DecodedBlock> blocks = decoder.DecodedBlocks[this.ComponentIndex];
blocks.Buffer[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block); blocks[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block);
} }
// for j // for j

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrImage.cs

@ -17,17 +17,17 @@ namespace ImageSharp.Formats.Jpg
/// <summary> /// <summary>
/// Gets the luminance components channel as <see cref="JpegPixelArea" />. /// Gets the luminance components channel as <see cref="JpegPixelArea" />.
/// </summary> /// </summary>
public JpegPixelArea YChannel; public Buffer2D<byte> YChannel;
/// <summary> /// <summary>
/// Gets the blue chroma components channel as <see cref="JpegPixelArea" />. /// Gets the blue chroma components channel as <see cref="JpegPixelArea" />.
/// </summary> /// </summary>
public JpegPixelArea CbChannel; public Buffer2D<byte> CbChannel;
/// <summary> /// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Cr channel /// Gets an offseted <see cref="JpegPixelArea" /> to the Cr channel
/// </summary> /// </summary>
public JpegPixelArea CrChannel; public Buffer2D<byte> CrChannel;
#pragma warning restore SA1401 #pragma warning restore SA1401
/// <summary> /// <summary>
@ -44,9 +44,9 @@ namespace ImageSharp.Formats.Jpg
this.YStride = width; this.YStride = width;
this.CStride = cSize.Width; this.CStride = cSize.Width;
this.YChannel = JpegPixelArea.CreatePooled(width, height); this.YChannel = Buffer2D<byte>.CreateClean(width, height);
this.CbChannel = JpegPixelArea.CreatePooled(cSize.Width, cSize.Height); this.CbChannel = Buffer2D<byte>.CreateClean(cSize.Width, cSize.Height);
this.CrChannel = JpegPixelArea.CreatePooled(cSize.Width, cSize.Height); this.CrChannel = Buffer2D<byte>.CreateClean(cSize.Width, cSize.Height);
} }
/// <summary> /// <summary>
@ -106,9 +106,9 @@ namespace ImageSharp.Formats.Jpg
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
this.YChannel.ReturnPooled(); this.YChannel.Dispose();
this.CbChannel.ReturnPooled(); this.CbChannel.Dispose();
this.CrChannel.ReturnPooled(); this.CrChannel.Dispose();
} }
/// <summary> /// <summary>

52
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -111,7 +111,7 @@ namespace ImageSharp.Formats
this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.Temp = new byte[2 * Block8x8F.ScalarCount];
this.ComponentArray = new Component[MaxComponents]; this.ComponentArray = new Component[MaxComponents];
this.DecodedBlocks = new DecodedBlockArray[MaxComponents]; this.DecodedBlocks = new Buffer<DecodedBlock>[MaxComponents];
} }
/// <summary> /// <summary>
@ -125,12 +125,12 @@ namespace ImageSharp.Formats
public HuffmanTree[] HuffmanTrees { get; } public HuffmanTree[] HuffmanTrees { get; }
/// <summary> /// <summary>
/// Gets the array of <see cref="DecodedBlockArray"/>-s storing the "raw" frequency-domain decoded blocks. /// Gets the array of <see cref="Buffer{T}"/>-s storing the "raw" frequency-domain decoded blocks.
/// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks. /// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks.
/// This is done by <see cref="ProcessBlocksIntoJpegImageChannels{TPixel}"/>. /// This is done by <see cref="ProcessBlocksIntoJpegImageChannels{TPixel}"/>.
/// When <see cref="IsProgressive"/>==true, we are touching these blocks multiple times - each time we process a Scan. /// When <see cref="IsProgressive"/>==true, we are touching these blocks multiple times - each time we process a Scan.
/// </summary> /// </summary>
public DecodedBlockArray[] DecodedBlocks { get; } public Buffer<DecodedBlock>[] DecodedBlocks { get; }
/// <summary> /// <summary>
/// Gets the quantization tables, in zigzag order. /// Gets the quantization tables, in zigzag order.
@ -216,15 +216,15 @@ namespace ImageSharp.Formats
this.HuffmanTrees[i].Dispose(); this.HuffmanTrees[i].Dispose();
} }
foreach (DecodedBlockArray blockArray in this.DecodedBlocks) foreach (Buffer<DecodedBlock> blockArray in this.DecodedBlocks)
{ {
blockArray.Dispose(); blockArray?.Dispose();
} }
this.ycbcrImage?.Dispose(); this.ycbcrImage?.Dispose();
this.InputProcessor.Dispose(); this.InputProcessor.Dispose();
this.grayImage.ReturnPooled(); this.grayImage.Pixels?.Dispose();
this.blackImage.ReturnPooled(); this.blackImage.Pixels?.Dispose();
} }
/// <summary> /// <summary>
@ -243,11 +243,11 @@ namespace ImageSharp.Formats
switch (compIndex) switch (compIndex)
{ {
case 0: case 0:
return this.ycbcrImage.YChannel; return new JpegPixelArea(this.ycbcrImage.YChannel);
case 1: case 1:
return this.ycbcrImage.CbChannel; return new JpegPixelArea(this.ycbcrImage.CbChannel);
case 2: case 2:
return this.ycbcrImage.CrChannel; return new JpegPixelArea(this.ycbcrImage.CrChannel);
case 3: case 3:
return this.blackImage; return this.blackImage;
default: default:
@ -586,9 +586,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < image.Width; x++) for (int x = 0; x < image.Width; x++)
{ {
byte cyan = this.ycbcrImage.YChannel.Pixels[yo + x]; byte cyan = this.ycbcrImage.YChannel[yo + x];
byte magenta = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)];
byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)];
TPixel packed = default(TPixel); TPixel packed = default(TPixel);
this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
@ -655,9 +655,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < image.Width; x++) for (int x = 0; x < image.Width; x++)
{ {
byte red = this.ycbcrImage.YChannel.Pixels[yo + x]; byte red = this.ycbcrImage.YChannel[yo + x];
byte green = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
byte blue = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
TPixel packed = default(TPixel); TPixel packed = default(TPixel);
packed.PackFromBytes(red, green, blue, 255); packed.PackFromBytes(red, green, blue, 255);
@ -687,9 +687,9 @@ namespace ImageSharp.Formats
y => y =>
{ {
// TODO. This Parallel loop doesn't give us the boost it should. // TODO. This Parallel loop doesn't give us the boost it should.
ref byte ycRef = ref this.ycbcrImage.YChannel.Pixels[0]; ref byte ycRef = ref this.ycbcrImage.YChannel[0];
ref byte cbRef = ref this.ycbcrImage.CbChannel.Pixels[0]; ref byte cbRef = ref this.ycbcrImage.CbChannel[0];
ref byte crRef = ref this.ycbcrImage.CrChannel.Pixels[0]; ref byte crRef = ref this.ycbcrImage.CrChannel[0];
fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables) fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables)
{ {
// TODO: Simplify + optimize + share duplicate code across converter methods // TODO: Simplify + optimize + share duplicate code across converter methods
@ -737,9 +737,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < image.Width; x++) for (int x = 0; x < image.Width; x++)
{ {
byte yy = this.ycbcrImage.YChannel.Pixels[yo + x]; byte yy = this.ycbcrImage.YChannel[yo + x];
byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
TPixel packed = default(TPixel); TPixel packed = default(TPixel);
this.PackYcck(ref packed, yy, cb, cr, x, y); this.PackYcck(ref packed, yy, cb, cr, x, y);
@ -787,7 +787,8 @@ namespace ImageSharp.Formats
if (this.ComponentCount == 1) if (this.ComponentCount == 1)
{ {
this.grayImage = JpegPixelArea.CreatePooled(8 * this.MCUCountX, 8 * this.MCUCountY); Buffer2D<byte> buffer = Buffer2D<byte>.CreateClean(8 * this.MCUCountX, 8 * this.MCUCountY);
this.grayImage = new JpegPixelArea(buffer);
} }
else else
{ {
@ -826,7 +827,8 @@ namespace ImageSharp.Formats
int h3 = this.ComponentArray[3].HorizontalFactor; int h3 = this.ComponentArray[3].HorizontalFactor;
int v3 = this.ComponentArray[3].VerticalFactor; int v3 = this.ComponentArray[3].VerticalFactor;
this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * this.MCUCountX, 8 * v3 * this.MCUCountY); Buffer2D<byte> buffer = Buffer2D<byte>.CreateClean(8 * h3 * this.MCUCountX, 8 * v3 * this.MCUCountY);
this.blackImage = new JpegPixelArea(buffer);
} }
} }
} }
@ -1308,7 +1310,7 @@ namespace ImageSharp.Formats
{ {
int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor
* this.ComponentArray[i].VerticalFactor; * this.ComponentArray[i].VerticalFactor;
this.DecodedBlocks[i] = new DecodedBlockArray(count); this.DecodedBlocks[i] = Buffer<DecodedBlock>.CreateClean(count);
} }
} }
} }

6
tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs

@ -61,9 +61,9 @@ namespace ImageSharp.Tests
//this.PrintChannel("Cb", img.CbChannel); //this.PrintChannel("Cb", img.CbChannel);
//this.PrintChannel("Cr", img.CrChannel); //this.PrintChannel("Cr", img.CrChannel);
Assert.Equal(img.YChannel.Stride, 400); Assert.Equal(img.YChannel.Width, 400);
Assert.Equal(img.CbChannel.Stride, 400 / expectedCStrideDiv); Assert.Equal(img.CbChannel.Width, 400 / expectedCStrideDiv);
Assert.Equal(img.CrChannel.Stride, 400 / expectedCStrideDiv); Assert.Equal(img.CrChannel.Width, 400 / expectedCStrideDiv);
} }
} }

Loading…
Cancel
Save