Browse Source

pooling PixelArea.Bytes

af/merge-core
Anton Firszov 10 years ago
parent
commit
661e32f143
  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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
@ -39,6 +39,11 @@ namespace ImageSharp
/// </remarks>
private bool isDisposed;
/// <summary>
/// True if <see cref="Bytes"/> was allocated by the constructor (rented from <see cref="BytesPool"/>)
/// </summary>
private bool isBufferOwner;
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
/// </summary>
@ -96,7 +101,7 @@ namespace ImageSharp
/// <param name="width">The width.</param>
/// <param name="componentOrder">The component order.</param>
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="padding">The number of bytes to pad each row.</param>
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.ComponentOrder = componentOrder;
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);
// TODO: Why is Resharper warning us about an impure method call?
@ -137,7 +143,7 @@ namespace ImageSharp
/// </summary>
~PixelArea()
{
this.Dispose();
this.Dispose(false);
}
/// <summary>
@ -145,20 +151,30 @@ namespace ImageSharp
/// </summary>
public byte[] Bytes { get; }
/// <summary>
/// Gets the component order.
/// </summary>
public ComponentOrder ComponentOrder { get; }
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.dataPointer;
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the data pointer.
/// </summary>
public byte* PixelBase { get; private set; }
/// <summary>
/// Gets the component order.
/// Gets number of bytes in a row.
/// </summary>
public ComponentOrder ComponentOrder { get; }
public int RowByteCount { get; }
/// <summary>
/// Gets the width.
@ -166,14 +182,17 @@ namespace ImageSharp
public int Width { get; }
/// <summary>
/// Gets the height.
/// Gets the pool used to rent <see cref="Bytes"/>, when it's not coming from an external source
/// </summary>
public int Height { get; }
private static ArrayPool<byte> BytesPool => ArrayPool<byte>.Shared;
/// <summary>
/// Gets number of bytes in a row.
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public int RowByteCount { get; }
public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Reads the stream to the area.
@ -193,39 +212,6 @@ namespace ImageSharp
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>
/// Resets the bytes of the array to it's initial value.
/// </summary>
@ -265,8 +251,45 @@ namespace ImageSharp
int requiredLength = (width * GetComponentCount(componentOrder)) * height;
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;
}
public static IEnumerable<string> AllJpegFiles => TestImages.Jpeg.All;
[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(
//Skip = "Benchmark, enable manually!"

Loading…
Cancel
Save