using System; using System.Collections.Generic; using System.Linq; using System.Drawing; using System.Drawing.Imaging; using System.IO; namespace FaceReplace { // NOTE: Since this data structure is 32-bits long, we don't need to worry about padding a scan line. public struct PixelData { /// The pixel format to use when using this structure. public const PixelFormat PixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppArgb; // NOTE: The order of these bytes are for the PixelFormat.Format32bppArgb format. public byte B; // Blue public byte G; // Green public byte R; // Red public byte A; // Alpha /// /// Creates a new instance from the specified . /// public PixelData(Color c) { A = c.A; R = c.R; G = c.G; B = c.B; } public PixelData(byte a, byte r, byte g, byte b) { A = a; R = r; G = g; B = b; } /// /// Converts this instance to a instance. /// /// public Color ToColor() { return Color.FromArgb(A, R, G, B); } /// /// Sets each component of this structure to the same as the specified color. /// /// public void SetFromColor(Color c) { this.R = c.R; this.G = c.G; this.B = c.B; this.A = c.A; } } /// /// This class is implemented so it only provides efficient operations out of the box. /// The purpose of this class is to enable pointer iteration thru an image to access /// its data much quicker. /// /// /// If you get the following: "error CS0227: Unsafe code may only appear if compiling with /unsafe" /// then you just need to check the following: Project Properties -> Build -> Allow unsafe code. /// public unsafe class FastBitmap : IDisposable { private bool _locked = false; private BitmapData _data; private bool _iMadeBitmap; private int _scanlineWidth; private byte* _firstByte; #region Properties /// Gets the image this instance is optimizing. public Bitmap Image { get; private set; } /// /// This is the rectangle that was opened on the image. If in the constructor, /// you didn't specify a rectangle, this rectangle is the entire image. /// public Rectangle ActiveImageRegion { get; private set; } /// Indicates whether the ActiveImageRegion was opened for writing. public bool IsWritable { get; private set; } #endregion #region Constructors /// /// This constructor makes a Bitmap object from the filename. /// /// It will copy the pixels that are in the imageRegion /// Rectangle. The imageRegion will be opened as readable /// but you can choose if it is to be opened as writable. /// /// public FastBitmap(String filename, Rectangle? imageRegion = null, bool openAsWritable = true) { if (String.IsNullOrEmpty(filename)) throw new ArgumentException("Must be specified.", "filename"); Image = new Bitmap(filename); _iMadeBitmap = true; Initialize(imageRegion, openAsWritable); } /// /// It will copy the pixels that are in the imageRegion /// Rectangle. The imageRegion will be opened as readable /// but you can choose if it is to be opened as writable. /// public FastBitmap(Bitmap bitmap, Rectangle? imageRegion = null, bool openAsWritable = true) { if (bitmap == null) throw new ArgumentNullException("bitmap"); Image = bitmap; Initialize(imageRegion, openAsWritable); } /// /// /// This constructor makes a Bitmap object from the stream. /// /// /// It will copy the pixels that are in the imageRegion /// Rectangle. The imageRegion will be opened as readable /// but you can choose if it is to be opened as writable. /// /// public FastBitmap(Stream stream, Rectangle? imageRegion = null, bool openAsWritable = true) { if (stream == null) throw new ArgumentNullException("stream"); Image = new Bitmap(stream); _iMadeBitmap = true; Initialize(imageRegion, openAsWritable); } #endregion #region Initialization, Lock/UnlockBitmap // Used by the ctors to finish initializing the image once we have a Bitmap instance. private void Initialize(Rectangle? imageRegion, bool openAsWritable) { System.Diagnostics.Debug.Assert(Image != null); IsWritable = openAsWritable; if (imageRegion != null) { // Make sure the rectangle is within the image bounds if (imageRegion.Value.Y < 0 || imageRegion.Value.X < 0 || imageRegion.Value.Right >= Image.Size.Width || imageRegion.Value.Bottom >= Image.Size.Height) throw new ArgumentOutOfRangeException("The rectangle goes outside of the image."); ActiveImageRegion = (Rectangle)imageRegion; } else ActiveImageRegion = new Rectangle(0, 0, Image.Width, Image.Height); LockBitmap(); } private void LockBitmap() { if (_locked) throw new InvalidOperationException("Already locked"); _data = Image.LockBits( ActiveImageRegion , IsWritable ? ImageLockMode.ReadWrite : ImageLockMode.ReadOnly , PixelData.PixelFormat ); // Depending on the sizeof(PixelData) the Stride may be padded for each row. _scanlineWidth = Math.Abs(_data.Stride); // Can be negative for some bitmaps _firstByte = (byte*)_data.Scan0; _locked = true; } private void UnlockBitmap() { if (!_locked) throw new InvalidOperationException("Not currently locked"); Image.UnlockBits(_data); _data = null; _firstByte = null; _locked = false; } #endregion /// /// Gets the pointer to the pixel at the specified location in the image. /// /// /// /// The pixel isn't within the bounds of the ActiveImageRegion. /// public PixelData* this[int x, int y] { get { // If the point (x,y) is out of bounds... if (x >= ActiveImageRegion.Width || y >= ActiveImageRegion.Height || x < 0 || y < 0) throw new IndexOutOfRangeException("Specified point is not inside of the opened image region."); return (PixelData*)(_firstByte + ((y - ActiveImageRegion.Y) * _scanlineWidth) + ((x - ActiveImageRegion.X) * sizeof(PixelData))); } } /// /// Gets a pointer to the first pixel in the a row of the ActiveImageRegion. /// Note, scan lines must start on a 4byte word boundary. Therefore, /// it isn't safe to start from the first pixel in the image and iterate /// thru all other pixels in the region just by doing a pixelDataPtr++. /// /// The y-coordinate of the row in the image (not in the ActiveImageRegion). /// public PixelData* GetInitialPixelForRow(int y) { if (y < ActiveImageRegion.Y || y >= ActiveImageRegion.Bottom) throw new IndexOutOfRangeException("The rowIdx is not within the bounds of the ActiveImageRegion."); return (PixelData*)(_firstByte + ((y - ActiveImageRegion.Y) * _scanlineWidth)); } public Color GetColor(int x, int y) { PixelData* pxData = this[x, y]; return pxData->ToColor(); } public void SetColor(int x, int y, Color c) { PixelData* pxData = this[x, y]; //pxData->SetFromColor(c); //*pxData = new PixelData(c); pxData->R = c.R; pxData->G = c.G; pxData->B = c.B; pxData->A = c.A; } /// /// Changes the to a different part of the image /// by unlocking the current region and locking the new region. /// All previous pointers from this structure will now be invalid. /// /// The new region to lock. public void ChangeActiveImageRegion(Rectangle region) { if (region.Y < 0 || region.X < 0 || region.Right >= Image.Size.Width || region.Bottom >= Image.Size.Height) throw new ArgumentOutOfRangeException("The region goes outside the bounds of the image."); if (_locked) UnlockBitmap(); ActiveImageRegion = region; LockBitmap(); } #region IDisposable Members public void Dispose() { if (_locked) UnlockBitmap(); if (_iMadeBitmap && Image != null) { Image.Dispose(); Image = null; } } #endregion } }