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
}
}