Browse Source

pooling PixelArea.Bytes

pull/58/head
Anton Firszov 10 years ago
parent
commit
f39687ba33
  1. 115
      src/ImageSharp/Image/PixelArea{TColor}.cs
  2. 11
      tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs

115
src/ImageSharp/Image/PixelArea{TColor}.cs

@ -2,10 +2,10 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp namespace ImageSharp
{ {
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -39,6 +39,11 @@ namespace ImageSharp
/// </remarks> /// </remarks>
private bool isDisposed; private bool isDisposed;
/// <summary>
/// True if <see cref="Bytes"/> was allocated by the constructor (rented from <see cref="BytesPool"/>)
/// </summary>
private bool isBufferOwner;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class. /// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
/// </summary> /// </summary>
@ -96,7 +101,7 @@ namespace ImageSharp
/// <param name="width">The width.</param> /// <param name="width">The width.</param>
/// <param name="componentOrder">The component order.</param> /// <param name="componentOrder">The component order.</param>
public PixelArea(int width, ComponentOrder componentOrder) public PixelArea(int width, ComponentOrder componentOrder)
: this(width, 1, componentOrder, 0) : this(width, 1, componentOrder, 0)
{ {
} }
@ -107,7 +112,7 @@ namespace ImageSharp
/// <param name="componentOrder">The component order.</param> /// <param name="componentOrder">The component order.</param>
/// <param name="padding">The number of bytes to pad each row.</param> /// <param name="padding">The number of bytes to pad each row.</param>
public PixelArea(int width, ComponentOrder componentOrder, int padding) public PixelArea(int width, ComponentOrder componentOrder, int padding)
: this(width, 1, componentOrder, padding) : this(width, 1, componentOrder, padding)
{ {
} }
@ -124,7 +129,8 @@ namespace ImageSharp
this.Height = height; this.Height = height;
this.ComponentOrder = componentOrder; this.ComponentOrder = componentOrder;
this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding; this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding;
this.Bytes = new byte[this.RowByteCount * height]; this.Bytes = BytesPool.Rent(this.RowByteCount * height);
this.isBufferOwner = true;
this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned);
// TODO: Why is Resharper warning us about an impure method call? // TODO: Why is Resharper warning us about an impure method call?
@ -137,7 +143,7 @@ namespace ImageSharp
/// </summary> /// </summary>
~PixelArea() ~PixelArea()
{ {
this.Dispose(); this.Dispose(false);
} }
/// <summary> /// <summary>
@ -145,20 +151,30 @@ namespace ImageSharp
/// </summary> /// </summary>
public byte[] Bytes { get; } public byte[] Bytes { get; }
/// <summary>
/// Gets the component order.
/// </summary>
public ComponentOrder ComponentOrder { get; }
/// <summary> /// <summary>
/// Gets the pointer to the pixel buffer. /// Gets the pointer to the pixel buffer.
/// </summary> /// </summary>
public IntPtr DataPointer => this.dataPointer; public IntPtr DataPointer => this.dataPointer;
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary> /// <summary>
/// Gets the data pointer. /// Gets the data pointer.
/// </summary> /// </summary>
public byte* PixelBase { get; private set; } public byte* PixelBase { get; private set; }
/// <summary> /// <summary>
/// Gets the component order. /// Gets number of bytes in a row.
/// </summary> /// </summary>
public ComponentOrder ComponentOrder { get; } public int RowByteCount { get; }
/// <summary> /// <summary>
/// Gets the width. /// Gets the width.
@ -166,14 +182,17 @@ namespace ImageSharp
public int Width { get; } public int Width { get; }
/// <summary> /// <summary>
/// Gets the height. /// Gets the pool used to rent <see cref="Bytes"/>, when it's not coming from an external source
/// </summary> /// </summary>
public int Height { get; } private static ArrayPool<byte> BytesPool => ArrayPool<byte>.Shared;
/// <summary> /// <summary>
/// Gets number of bytes in a row. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
public int RowByteCount { get; } public void Dispose()
{
this.Dispose(true);
}
/// <summary> /// <summary>
/// Reads the stream to the area. /// Reads the stream to the area.
@ -193,39 +212,6 @@ namespace ImageSharp
stream.Write(this.Bytes, 0, this.Bytes.Length); stream.Write(this.Bytes, 0, this.Bytes.Length);
} }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
if (this.PixelBase == null)
{
return;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
this.dataPointer = IntPtr.Zero;
this.PixelBase = null;
this.isDisposed = true;
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
/// <summary> /// <summary>
/// Resets the bytes of the array to it's initial value. /// Resets the bytes of the array to it's initial value.
/// </summary> /// </summary>
@ -265,8 +251,45 @@ namespace ImageSharp
int requiredLength = (width * GetComponentCount(componentOrder)) * height; int requiredLength = (width * GetComponentCount(componentOrder)) * height;
if (bytes.Length != requiredLength) if (bytes.Length != requiredLength)
{ {
throw new ArgumentOutOfRangeException(nameof(bytes), $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); throw new ArgumentOutOfRangeException(
nameof(bytes),
$"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}.");
} }
} }
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (this.PixelBase == null)
{
return;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
if (disposing && this.isBufferOwner)
{
BytesPool.Return(this.Bytes);
}
this.dataPointer = IntPtr.Zero;
this.PixelBase = null;
this.isDisposed = true;
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
} }
} }

11
tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs

@ -19,6 +19,7 @@ namespace ImageSharp.Tests
{ {
this.Output = output; this.Output = output;
} }
public static IEnumerable<string> AllJpegFiles => TestImages.Jpeg.All; public static IEnumerable<string> AllJpegFiles => TestImages.Jpeg.All;
[Theory] [Theory]
@ -57,7 +58,15 @@ namespace ImageSharp.Tests
} }
} }
private const int BenchmarkExecTimes = 2; private const int BenchmarkExecTimes = 30;
public static readonly string[] BenchmarkFiles =
{
TestImages.Bmp.Car, TestImages.Bmp.NegHeight,
TestImages.Bmp.F, TestImages.Png.Splash,
TestImages.Jpeg.Jpeg420, TestImages.Jpeg.Calliphora,
TestImages.Jpeg.Cmyk
};
[Theory( [Theory(
//Skip = "Benchmark, enable manually!" //Skip = "Benchmark, enable manually!"

Loading…
Cancel
Save