mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
10 KiB
295 lines
10 KiB
// <copyright file="PixelArea{TColor}.cs" company="James Jackson-South">
|
|
// 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;
|
|
using System.Runtime.InteropServices;
|
|
|
|
/// <summary>
|
|
/// Represents an area of generic <see cref="Image{TColor}"/> pixels.
|
|
/// </summary>
|
|
/// <typeparam name="TColor">The pixel format.</typeparam>
|
|
public sealed unsafe class PixelArea<TColor> : IDisposable
|
|
where TColor : struct, IPackedPixel, IEquatable<TColor>
|
|
{
|
|
/// <summary>
|
|
/// Provides a way to access the pixels from unmanaged memory.
|
|
/// </summary>
|
|
private readonly GCHandle pixelsHandle;
|
|
|
|
/// <summary>
|
|
/// The pointer to the pixel buffer.
|
|
/// </summary>
|
|
private IntPtr dataPointer;
|
|
|
|
/// <summary>
|
|
/// A value indicating whether this instance of the given entity has been disposed.
|
|
/// </summary>
|
|
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
|
/// <remarks>
|
|
/// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity
|
|
/// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's
|
|
/// life in the Garbage Collector.
|
|
/// </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>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="bytes">The bytes.</param>
|
|
/// <param name="componentOrder">The component order.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// Thrown if <paramref name="bytes"></paramref> is the incorrect length.
|
|
/// </exception>
|
|
public PixelArea(int width, byte[] bytes, ComponentOrder componentOrder)
|
|
: this(width, 1, bytes, componentOrder)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="height">The height.</param>
|
|
/// <param name="bytes">The bytes.</param>
|
|
/// <param name="componentOrder">The component order.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// Thrown if <paramref name="bytes"></paramref> is the incorrect length.
|
|
/// </exception>
|
|
public PixelArea(int width, int height, byte[] bytes, ComponentOrder componentOrder)
|
|
{
|
|
this.CheckBytesLength(width, height, bytes, componentOrder);
|
|
|
|
this.Width = width;
|
|
this.Height = height;
|
|
this.ComponentOrder = componentOrder;
|
|
this.RowByteCount = width * GetComponentCount(componentOrder);
|
|
this.Bytes = bytes;
|
|
this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned);
|
|
|
|
// TODO: Why is Resharper warning us about an impure method call?
|
|
this.dataPointer = this.pixelsHandle.AddrOfPinnedObject();
|
|
this.PixelBase = (byte*)this.dataPointer.ToPointer();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="height">The height.</param>
|
|
/// <param name="componentOrder">The component order.</param>
|
|
public PixelArea(int width, int height, ComponentOrder componentOrder)
|
|
: this(width, height, componentOrder, 0)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="componentOrder">The component order.</param>
|
|
public PixelArea(int width, ComponentOrder componentOrder)
|
|
: this(width, 1, componentOrder, 0)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="width">The width. </param>
|
|
/// <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)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="height">The height.</param>
|
|
/// <param name="componentOrder">The component order.</param>
|
|
/// <param name="padding">The number of bytes to pad each row.</param>
|
|
public PixelArea(int width, int height, ComponentOrder componentOrder, int padding)
|
|
{
|
|
this.Width = width;
|
|
this.Height = height;
|
|
this.ComponentOrder = componentOrder;
|
|
this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding;
|
|
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?
|
|
this.dataPointer = this.pixelsHandle.AddrOfPinnedObject();
|
|
this.PixelBase = (byte*)this.dataPointer.ToPointer();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizes an instance of the <see cref="PixelArea{TColor}"/> class.
|
|
/// </summary>
|
|
~PixelArea()
|
|
{
|
|
this.Dispose(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the data in bytes.
|
|
/// </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 number of bytes in a row.
|
|
/// </summary>
|
|
public int RowByteCount { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the width.
|
|
/// </summary>
|
|
public int Width { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the pool used to rent <see cref="Bytes"/>, when it's not coming from an external source
|
|
/// </summary>
|
|
private static ArrayPool<byte> BytesPool => ArrayPool<byte>.Shared;
|
|
|
|
/// <summary>
|
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
this.Dispose(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the stream to the area.
|
|
/// </summary>
|
|
/// <param name="stream">The stream.</param>
|
|
public void Read(Stream stream)
|
|
{
|
|
stream.Read(this.Bytes, 0, this.Bytes.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the area to the stream.
|
|
/// </summary>
|
|
/// <param name="stream">The stream.</param>
|
|
public void Write(Stream stream)
|
|
{
|
|
stream.Write(this.Bytes, 0, this.Bytes.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the bytes of the array to it's initial value.
|
|
/// </summary>
|
|
internal void Reset()
|
|
{
|
|
Unsafe.InitBlock(this.PixelBase, 0, (uint)this.Bytes.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets component count for the given order.
|
|
/// </summary>
|
|
/// <param name="componentOrder">The component order.</param>
|
|
/// <returns>
|
|
/// The <see cref="int"/>.
|
|
/// </returns>
|
|
/// <exception cref="NotSupportedException">
|
|
/// Thrown if an invalid order is given.
|
|
/// </exception>
|
|
private static int GetComponentCount(ComponentOrder componentOrder)
|
|
{
|
|
switch (componentOrder)
|
|
{
|
|
case ComponentOrder.ZYX:
|
|
case ComponentOrder.XYZ:
|
|
return 3;
|
|
case ComponentOrder.ZYXW:
|
|
case ComponentOrder.XYZW:
|
|
return 4;
|
|
}
|
|
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void CheckBytesLength(int width, int height, byte[] bytes, ComponentOrder componentOrder)
|
|
{
|
|
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}.");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|