// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // // // Allows fast access to 's pixel data. // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging { using System; using System.Drawing; using System.Drawing.Imaging; using ImageProcessor.Imaging.Colors; /// /// Allows fast access to 's pixel data. /// public unsafe class FastBitmap : IDisposable { /// /// The bitmap. /// private readonly Bitmap bitmap; /// /// The width of the bitmap. /// private readonly int width; /// /// The height of the bitmap. /// private readonly int height; /// /// The number of bytes in a row. /// private int bytesInARow; /// /// The size of the color32 structure. /// private int color32Size; /// /// The bitmap data. /// private BitmapData bitmapData; /// /// The pixel buffer for holding pixel data. /// private byte* pixelBuffer; /// /// A value indicating whether this instance of the given entity has been disposed. /// /// if this instance has been disposed; otherwise, . /// /// 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. /// private bool isDisposed; /// /// Initializes a new instance of the class. /// /// The input bitmap. public FastBitmap(Image bitmap) { this.bitmap = (Bitmap)bitmap; this.width = this.bitmap.Width; this.height = this.bitmap.Height; this.LockBitmap(); } /// /// Gets the width, in pixels of the . /// public int Width { get { return this.width; } } /// /// Gets the height, in pixels of the . /// public int Height { get { return this.height; } } /// /// Gets the pixel data for the given position. /// /// /// The x position of the pixel. /// /// /// The y position of the pixel. /// /// /// The . /// private Color32* this[int x, int y] { get { return (Color32*)(this.pixelBuffer + (y * this.bytesInARow) + (x * this.color32Size)); } } /// /// Allows the implicit conversion of an instance of to a /// . /// /// /// The instance of to convert. /// /// /// An instance of . /// public static implicit operator Image(FastBitmap fastBitmap) { return fastBitmap.bitmap; } /// /// Allows the implicit conversion of an instance of to a /// . /// /// /// The instance of to convert. /// /// /// An instance of . /// public static implicit operator Bitmap(FastBitmap fastBitmap) { return fastBitmap.bitmap; } /// /// Gets the color at the specified pixel of the . /// /// The x-coordinate of the pixel to retrieve. /// The y-coordinate of the pixel to retrieve. /// The at the given pixel. 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); } /// /// Sets the color of the specified pixel of the . /// /// The x-coordinate of the pixel to set. /// The y-coordinate of the pixel to set. /// /// A color structure that represents the /// color to set the specified pixel. /// 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; } /// /// Disposes the object and frees resources for the Garbage Collector. /// 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); } /// /// Determines whether the specified is equal to the current . /// /// /// true if the specified object is equal to the current object; otherwise, false. /// /// The object to compare with the current object. public override bool Equals(object obj) { FastBitmap fastBitmap = obj as FastBitmap; if (fastBitmap == null) { return false; } return this.bitmap == fastBitmap.bitmap; } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// public override int GetHashCode() { return this.bitmap.GetHashCode(); } /// /// Disposes the object and frees resources for the Garbage Collector. /// /// If true, the object gets disposed. 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; } /// /// Locks the bitmap into system memory. /// 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(); } /// /// Unlocks the bitmap from system memory. /// 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; } } }