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;
/// <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>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct JpegBlockProcessor
@ -47,10 +47,10 @@ namespace ImageSharp.Formats.Jpg
/// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param>
public void ProcessAllBlocks(JpegDecoderCore decoder)
{
DecodedBlockArray blockArray = decoder.DecodedBlocks[this.componentIndex];
for (int i = 0; i < blockArray.Count; i++)
Buffer<DecodedBlock> blockArray = decoder.DecodedBlocks[this.componentIndex];
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>
namespace ImageSharp.Formats.Jpg
{
using System.Buffers;
using System.Runtime.CompilerServices;
/// <summary>
@ -15,20 +14,30 @@ namespace ImageSharp.Formats.Jpg
/// <summary>
/// Initializes a new instance of the <see cref="JpegPixelArea" /> struct from existing data.
/// </summary>
/// <param name="pixels">The pixel array</param>
/// <param name="striede">The stride</param>
/// <param name="pixels">The pixel buffer</param>
/// <param name="stride">The stride</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.Offset = offset;
}
/// <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>
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>
/// 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;
/// <summary>
/// Gets or the stride.
/// Gets the stride.
/// </summary>
public int Stride { get; }
/// <summary>
/// Gets or the offset.
/// Gets the offset.
/// </summary>
public int Offset { get; }
/// <summary>
/// Gets a <see cref="MutableSpan{T}" /> of bytes to the pixel area
/// </summary>
public MutableSpan<byte> Span => new MutableSpan<byte>(this.Pixels, this.Offset);
private static ArrayPool<byte> BytePool => ArrayPool<byte>.Shared;
public MutableSpan<byte> Span => new MutableSpan<byte>(this.Pixels.Array, this.Offset);
/// <summary>
/// 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>
/// Gets the subarea that belongs to the Block8x8 defined by block indices
/// </summary>
@ -129,7 +107,7 @@ namespace ImageSharp.Formats.Jpg
public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp)
{
// 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):
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)
{
@ -179,8 +179,8 @@ namespace ImageSharp.Formats.Jpg
}
// Store the decoded block
DecodedBlockArray blocks = decoder.DecodedBlocks[this.ComponentIndex];
blocks.Buffer[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block);
Buffer<DecodedBlock> blocks = decoder.DecodedBlocks[this.ComponentIndex];
blocks[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block);
}
// for j

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

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

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

@ -111,7 +111,7 @@ namespace ImageSharp.Formats
this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.ScalarCount];
this.ComponentArray = new Component[MaxComponents];
this.DecodedBlocks = new DecodedBlockArray[MaxComponents];
this.DecodedBlocks = new Buffer<DecodedBlock>[MaxComponents];
}
/// <summary>
@ -125,12 +125,12 @@ namespace ImageSharp.Formats
public HuffmanTree[] HuffmanTrees { get; }
/// <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.
/// 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.
/// </summary>
public DecodedBlockArray[] DecodedBlocks { get; }
public Buffer<DecodedBlock>[] DecodedBlocks { get; }
/// <summary>
/// Gets the quantization tables, in zigzag order.
@ -216,15 +216,15 @@ namespace ImageSharp.Formats
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.InputProcessor.Dispose();
this.grayImage.ReturnPooled();
this.blackImage.ReturnPooled();
this.grayImage.Pixels?.Dispose();
this.blackImage.Pixels?.Dispose();
}
/// <summary>
@ -243,11 +243,11 @@ namespace ImageSharp.Formats
switch (compIndex)
{
case 0:
return this.ycbcrImage.YChannel;
return new JpegPixelArea(this.ycbcrImage.YChannel);
case 1:
return this.ycbcrImage.CbChannel;
return new JpegPixelArea(this.ycbcrImage.CbChannel);
case 2:
return this.ycbcrImage.CrChannel;
return new JpegPixelArea(this.ycbcrImage.CrChannel);
case 3:
return this.blackImage;
default:
@ -586,9 +586,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < image.Width; x++)
{
byte cyan = this.ycbcrImage.YChannel.Pixels[yo + x];
byte magenta = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
byte cyan = this.ycbcrImage.YChannel[yo + x];
byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)];
byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)];
TPixel packed = default(TPixel);
this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
@ -655,9 +655,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < image.Width; x++)
{
byte red = this.ycbcrImage.YChannel.Pixels[yo + x];
byte green = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte blue = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
byte red = this.ycbcrImage.YChannel[yo + x];
byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
TPixel packed = default(TPixel);
packed.PackFromBytes(red, green, blue, 255);
@ -687,9 +687,9 @@ namespace ImageSharp.Formats
y =>
{
// TODO. This Parallel loop doesn't give us the boost it should.
ref byte ycRef = ref this.ycbcrImage.YChannel.Pixels[0];
ref byte cbRef = ref this.ycbcrImage.CbChannel.Pixels[0];
ref byte crRef = ref this.ycbcrImage.CrChannel.Pixels[0];
ref byte ycRef = ref this.ycbcrImage.YChannel[0];
ref byte cbRef = ref this.ycbcrImage.CbChannel[0];
ref byte crRef = ref this.ycbcrImage.CrChannel[0];
fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables)
{
// TODO: Simplify + optimize + share duplicate code across converter methods
@ -737,9 +737,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < image.Width; x++)
{
byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
byte yy = this.ycbcrImage.YChannel[yo + x];
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
TPixel packed = default(TPixel);
this.PackYcck(ref packed, yy, cb, cr, x, y);
@ -787,7 +787,8 @@ namespace ImageSharp.Formats
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
{
@ -826,7 +827,8 @@ namespace ImageSharp.Formats
int h3 = this.ComponentArray[3].HorizontalFactor;
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
* 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("Cr", img.CrChannel);
Assert.Equal(img.YChannel.Stride, 400);
Assert.Equal(img.CbChannel.Stride, 400 / expectedCStrideDiv);
Assert.Equal(img.CrChannel.Stride, 400 / expectedCStrideDiv);
Assert.Equal(img.YChannel.Width, 400);
Assert.Equal(img.CbChannel.Width, 400 / expectedCStrideDiv);
Assert.Equal(img.CrChannel.Width, 400 / expectedCStrideDiv);
}
}

Loading…
Cancel
Save