// -------------------------------------------------------------------------------------------------------------------- // // 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; /// /// 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 pixel data. /// private int pixelDataSize; /// /// 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(Bitmap bitmap) { this.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 PixelData* this[int x, int y] { get { return (PixelData*)(this.pixelBuffer + (y * this.bytesInARow) + (x * this.pixelDataSize)); } } /// /// 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 ((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."); } PixelData* 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 ((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."); } PixelData* 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); } /// /// 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.pixelDataSize = sizeof(PixelData); this.bytesInARow = bounds.Width * this.pixelDataSize; 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; } } }