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.
308 lines
10 KiB
308 lines
10 KiB
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="FastBitmap.cs" company="James South">
|
|
// Copyright (c) James South.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
// </copyright>
|
|
// <summary>
|
|
// Allows fast access to <see cref="System.Drawing.Bitmap" />'s pixel data.
|
|
// </summary>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
namespace ImageProcessor.Imaging
|
|
{
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
|
|
using ImageProcessor.Imaging.Colors;
|
|
|
|
/// <summary>
|
|
/// Allows fast access to <see cref="System.Drawing.Bitmap"/>'s pixel data.
|
|
/// </summary>
|
|
public unsafe class FastBitmap : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// The bitmap.
|
|
/// </summary>
|
|
private readonly Bitmap bitmap;
|
|
|
|
/// <summary>
|
|
/// The width of the bitmap.
|
|
/// </summary>
|
|
private readonly int width;
|
|
|
|
/// <summary>
|
|
/// The height of the bitmap.
|
|
/// </summary>
|
|
private readonly int height;
|
|
|
|
/// <summary>
|
|
/// The number of bytes in a row.
|
|
/// </summary>
|
|
private int bytesInARow;
|
|
|
|
/// <summary>
|
|
/// The size of the color32 structure.
|
|
/// </summary>
|
|
private int color32Size;
|
|
|
|
/// <summary>
|
|
/// The bitmap data.
|
|
/// </summary>
|
|
private BitmapData bitmapData;
|
|
|
|
/// <summary>
|
|
/// The pixel buffer for holding pixel data.
|
|
/// </summary>
|
|
private byte* pixelBuffer;
|
|
|
|
/// <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>
|
|
/// Initializes a new instance of the <see cref="FastBitmap"/> class.
|
|
/// </summary>
|
|
/// <param name="bitmap">The input bitmap.</param>
|
|
public FastBitmap(Image bitmap)
|
|
{
|
|
this.bitmap = (Bitmap)bitmap;
|
|
this.width = this.bitmap.Width;
|
|
this.height = this.bitmap.Height;
|
|
this.LockBitmap();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the width, in pixels of the <see cref="System.Drawing.Bitmap"/>.
|
|
/// </summary>
|
|
public int Width
|
|
{
|
|
get
|
|
{
|
|
return this.width;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the height, in pixels of the <see cref="System.Drawing.Bitmap"/>.
|
|
/// </summary>
|
|
public int Height
|
|
{
|
|
get
|
|
{
|
|
return this.height;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the pixel data for the given position.
|
|
/// </summary>
|
|
/// <param name="x">
|
|
/// The x position of the pixel.
|
|
/// </param>
|
|
/// <param name="y">
|
|
/// The y position of the pixel.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The <see cref="Color32"/>.
|
|
/// </returns>
|
|
private Color32* this[int x, int y]
|
|
{
|
|
get { return (Color32*)(this.pixelBuffer + (y * this.bytesInARow) + (x * this.color32Size)); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows the implicit conversion of an instance of <see cref="FastBitmap"/> to a
|
|
/// <see cref="System.Drawing.Image"/>.
|
|
/// </summary>
|
|
/// <param name="fastBitmap">
|
|
/// The instance of <see cref="FastBitmap"/> to convert.
|
|
/// </param>
|
|
/// <returns>
|
|
/// An instance of <see cref="System.Drawing.Image"/>.
|
|
/// </returns>
|
|
public static implicit operator Image(FastBitmap fastBitmap)
|
|
{
|
|
return fastBitmap.bitmap;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows the implicit conversion of an instance of <see cref="FastBitmap"/> to a
|
|
/// <see cref="System.Drawing.Bitmap"/>.
|
|
/// </summary>
|
|
/// <param name="fastBitmap">
|
|
/// The instance of <see cref="FastBitmap"/> to convert.
|
|
/// </param>
|
|
/// <returns>
|
|
/// An instance of <see cref="System.Drawing.Bitmap"/>.
|
|
/// </returns>
|
|
public static implicit operator Bitmap(FastBitmap fastBitmap)
|
|
{
|
|
return fastBitmap.bitmap;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the color at the specified pixel of the <see cref="System.Drawing.Bitmap"/>.
|
|
/// </summary>
|
|
/// <param name="x">The x-coordinate of the pixel to retrieve.</param>
|
|
/// <param name="y">The y-coordinate of the pixel to retrieve.</param>
|
|
/// <returns>The <see cref="System.Drawing.Color"/> at the given pixel.</returns>
|
|
public Color GetPixel(int x, int y)
|
|
{
|
|
#if DEBUG
|
|
if ((x < 0) || (x >= this.width))
|
|
{
|
|
throw new ArgumentOutOfRangeException("x", "Value cannot be less than zero or greater than the bitmap width.");
|
|
}
|
|
|
|
if ((y < 0) || (y >= this.height))
|
|
{
|
|
throw new ArgumentOutOfRangeException("y", "Value cannot be less than zero or greater than the bitmap height.");
|
|
}
|
|
#endif
|
|
Color32* data = this[x, y];
|
|
return Color.FromArgb(data->A, data->R, data->G, data->B);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the color of the specified pixel of the <see cref="System.Drawing.Bitmap"/>.
|
|
/// </summary>
|
|
/// <param name="x">The x-coordinate of the pixel to set.</param>
|
|
/// <param name="y">The y-coordinate of the pixel to set.</param>
|
|
/// <param name="color">
|
|
/// A <see cref="System.Drawing.Color"/> color structure that represents the
|
|
/// color to set the specified pixel.
|
|
/// </param>
|
|
public void SetPixel(int x, int y, Color color)
|
|
{
|
|
#if DEBUG
|
|
if ((x < 0) || (x >= this.width))
|
|
{
|
|
throw new ArgumentOutOfRangeException("x", "Value cannot be less than zero or greater than the bitmap width.");
|
|
}
|
|
|
|
if ((y < 0) || (y >= this.height))
|
|
{
|
|
throw new ArgumentOutOfRangeException("y", "Value cannot be less than zero or greater than the bitmap height.");
|
|
}
|
|
#endif
|
|
Color32* data = this[x, y];
|
|
data->R = color.R;
|
|
data->G = color.G;
|
|
data->B = color.B;
|
|
data->A = color.A;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes the object and frees resources for the Garbage Collector.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
this.Dispose(true);
|
|
|
|
// This object will be cleaned up by the Dispose method.
|
|
// Therefore, you should call GC.SupressFinalize to
|
|
// take this object off the finalization queue
|
|
// and prevent finalization code for this object
|
|
// from executing a second time.
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// true if the specified object is equal to the current object; otherwise, false.
|
|
/// </returns>
|
|
/// <param name="obj">The object to compare with the current object. </param>
|
|
public override bool Equals(object obj)
|
|
{
|
|
FastBitmap fastBitmap = obj as FastBitmap;
|
|
|
|
if (fastBitmap == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return this.bitmap == fastBitmap.bitmap;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serves as a hash function for a particular type.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A hash code for the current <see cref="T:System.Object"/>.
|
|
/// </returns>
|
|
public override int GetHashCode()
|
|
{
|
|
return this.bitmap.GetHashCode();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes the object and frees resources for the Garbage Collector.
|
|
/// </summary>
|
|
/// <param name="disposing">If true, the object gets disposed.</param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (this.isDisposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
// Dispose of any managed resources here.
|
|
this.UnlockBitmap();
|
|
}
|
|
|
|
// Call the appropriate methods to clean up
|
|
// unmanaged resources here.
|
|
// Note disposing is done.
|
|
this.isDisposed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Locks the bitmap into system memory.
|
|
/// </summary>
|
|
private void LockBitmap()
|
|
{
|
|
Rectangle bounds = new Rectangle(Point.Empty, this.bitmap.Size);
|
|
|
|
// Figure out the number of bytes in a row. This is rounded up to be a multiple
|
|
// of 4 bytes, since a scan line in an image must always be a multiple of 4 bytes
|
|
// in length.
|
|
this.color32Size = sizeof(Color32);
|
|
this.bytesInARow = bounds.Width * this.color32Size;
|
|
if (this.bytesInARow % 4 != 0)
|
|
{
|
|
this.bytesInARow = 4 * ((this.bytesInARow / 4) + 1);
|
|
}
|
|
|
|
// Lock the bitmap
|
|
this.bitmapData = this.bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
|
|
|
// Copy the bitmap data across to the array for manipulation.
|
|
this.pixelBuffer = (byte*)this.bitmapData.Scan0.ToPointer();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlocks the bitmap from system memory.
|
|
/// </summary>
|
|
private void UnlockBitmap()
|
|
{
|
|
// Copy the RGB values back to the bitmap and unlock the bitmap.
|
|
this.bitmap.UnlockBits(this.bitmapData);
|
|
this.bitmapData = null;
|
|
this.pixelBuffer = null;
|
|
}
|
|
}
|
|
}
|
|
|