using System;
using System.Drawing;
using System.Drawing.Imaging;
using Pfz.Drawing.Extensions.ColorExtensions;
using Pfz.Threading;
namespace Pfz.Drawing
{
/// <summary>
/// This class represents a bitmap as a two dimensional array of Colors.
/// It is capable of generating real bitmaps and extracting real-bitmap colors
/// so you don't need to bother using unsafe code. Also, working with this
/// ColorBitmap instead of using System.Drawing.Bitmap with GetPixel and
/// SetPixel is about 8 times faster.
/// </summary>
[Serializable]
public sealed class ColorBitmap:
IBitmap<Color>
{
#region Constructors
#region Internal default constructor
internal ColorBitmap()
{
}
#endregion
#region Create Empty
/// <summary>
/// Creates a new black managed-bitmap with the given width and height.
/// </summary>
/// <param name="width">The width of the new bitmap.</param>
/// <param name="height">The height of the new bitmap.</param>
public ColorBitmap(int width, int height)
{
PixelArray = new Color[width * height];
Width = width;
}
#endregion
#region Create from pixelArray
/// <summary>
/// Creates a new bitmap using the given array of colors and the given width.
/// The array length must be a multiple of the width, as each line
/// "width in length" is considered to be one line.
/// </summary>
/// <param name="pixelArray">The array of colors that compose the bitmap.</param>
/// <param name="width">The width of the bitmap.</param>
public ColorBitmap(Color[] pixelArray, int width)
{
if (pixelArray == null)
throw new ArgumentNullException("pixelArray");
if (width < 1)
throw new ArgumentOutOfRangeException("width");
if ((pixelArray.Length % width) != 0)
throw new ArithmeticException("pixelArray.Length must be a multiple of width.");
PixelArray = pixelArray;
Width = width;
}
#endregion
#endregion
#region Properties
#region PixelArray
/// <summary>
/// Gets all the pixels of the Bitmap as a single color array.
/// </summary>
public Color[] PixelArray { get; internal set; }
#endregion
#region Width
/// <summary>
/// Gets the Width of the bitmap.
/// </summary>
public int Width { get; internal set; }
#endregion
#region Height
/// <summary>
/// Gets the Height of the bitmap.
/// </summary>
public int Height
{
get
{
return PixelArray.Length / Width;
}
}
#endregion
#region this[x, y]
/// <summary>
/// Gets or sets the pixel color at the given location.
/// </summary>
/// <param name="x">
/// The x location of the pixel. This is not checked to be inside a line, so values lower than zero or higher than Width will draw on other lines.
/// The only guarantee is that you will never access memory positions outside the ColorArray.
/// </param>
/// <param name="y">The y position of the pixel.</param>
/// <returns>The color at the given coordinate.</returns>
public Color this[int x, int y]
{
get
{
return PixelArray[y * Width + x];
}
set
{
PixelArray[y * Width + x] = value;
}
}
#endregion
#endregion
#region Methods
#region p_Initialize
private void p_Initialize(Bitmap bitmap, Rectangle area)
{
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
throw new ArgumentException("bitmap.PixelFormat must be Format32bppArgb.");
Width = area.Width;
int count = Width * area.Height;
PixelArray = new Color[count];
BitmapData data = null;
AbortSafe.Run
(
() => data = bitmap.LockBits(area, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb),
() =>
{
unsafe
{
int index = -1;
byte *scanLineBytes = (byte *)data.Scan0.ToPointer();
for (int y = 0; y < area.Height; y++)
{
int *scanLine = (int *)scanLineBytes;
for (int x = 0; x < area.Width; x++)
{
int intColor = scanLine[x];
Color color = Color.FromArgb(intColor);
index++;
PixelArray[index] = color;
}
scanLineBytes += data.Stride;
}
}
},
() =>
{
if (data != null)
bitmap.UnlockBits(data);
}
);
}
#endregion
#region MakeGrayscale
/// <summary>
/// Makes the actual bitmap grayscale. Note that the bitmap is kept as 32-bit
/// in memory, so you can add colored pixels after calling this method.
/// </summary>
public void MakeGrayscale()
{
int count = PixelArray.Length;
for (int i=0; i<count; i++)
{
Color color = PixelArray[i];
color = color.AsGrayscale();
PixelArray[i] = color;
}
}
#endregion
#region CopyBlockFrom
/// <summary>
/// Copies a block from the given System.Drawing.Bitmap.
/// </summary>
public void CopyBlockFrom(Bitmap sourceBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
{
if (sourceBitmap == null)
throw new ArgumentNullException("sourceBitmap");
if (sourceBitmap.PixelFormat != PixelFormat.Format32bppArgb)
throw new ArgumentException("sourceBitmap.PixelFormat must be Format32bppArgb.");
if (ManagedBitmap.i_ValidateCopyToParameters(sourceBitmap.Size, new Size(Width, Height), sourceLocation, destinationLocation, blockSize))
return;
BitmapData data = null;
AbortSafe.Run
(
() => data = sourceBitmap.LockBits(new Rectangle(sourceLocation, blockSize), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb),
() =>
{
unsafe
{
byte *scanLineBytes = (byte *)data.Scan0.ToPointer();
int thisScanline = destinationLocation.Y * Width + destinationLocation.X;
for (int y = 0; y < blockSize.Height; y++)
{
int *scanLine = (int *)scanLineBytes;
for (int x = 0; x < blockSize.Width; x++)
PixelArray[thisScanline + x] = Color.FromArgb(scanLine[x]);
scanLineBytes += data.Stride;
thisScanline += Width;
}
}
},
() =>
{
if (data != null)
sourceBitmap.UnlockBits(data);
}
);
}
#endregion
#region CopyBlockTo
/// <summary>
/// Draws this bitmap over a System.Drawing.Bitmap at the given coordinates.
/// Note that alpha values will be copied to the destination bitmap, instead of
/// merging the colors.
/// </summary>
public void CopyBlockTo(Bitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
{
if (destinationBitmap == null)
throw new ArgumentNullException("destinationBitmap");
if (destinationBitmap.PixelFormat != PixelFormat.Format32bppArgb)
throw new ArgumentException("destinationBitmap.PixelFormat must be Format32bppArgb.");
if (ManagedBitmap.i_ValidateCopyToParameters(new Size(Width, Height), destinationBitmap.Size, sourceLocation, destinationLocation, blockSize))
return;
BitmapData data = null;
AbortSafe.Run
(
() => data = destinationBitmap.LockBits(new Rectangle(destinationLocation, blockSize), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb),
() =>
{
unsafe
{
byte *scanLineBytes = (byte *)data.Scan0.ToPointer();
for (int y = 0; y < blockSize.Height; y++)
{
int *scanLine = (int *)scanLineBytes;
int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
for (int x = 0; x < blockSize.Width; x++)
scanLine[x] = PixelArray[sourceYIndex + x].ToArgb();
scanLineBytes += data.Stride;
}
}
},
() =>
{
if (data != null)
destinationBitmap.UnlockBits(data);
}
);
}
/// <summary>
/// Copies a block from this bitmap to the given bitmap.
/// The destination bitmap can be the same, but the blocks can't intersect.
/// </summary>
public void CopyBlockTo(ColorBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
{
if (ManagedBitmap.i_ValidateCopyToParameters(this, destinationBitmap, sourceLocation, destinationLocation, blockSize))
return;
if (destinationBitmap == this)
{
Rectangle sourceRectangle = new Rectangle(sourceLocation, blockSize);
Rectangle destinationRectangle = new Rectangle(destinationLocation, blockSize);
if (sourceRectangle.IntersectsWith(destinationRectangle))
throw new ArgumentException("When copying a block to the same bitmap, the source and destination blocks can't intersect with each other.");
}
var destPixels = destinationBitmap.PixelArray;
for (int y = 0; y < blockSize.Height; y++)
{
int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
int destinationYIndex = ((destinationLocation.Y + y) * Width) + destinationLocation.X;
for (int x = 0; x < blockSize.Width; x++)
destPixels[destinationYIndex + x] = PixelArray[sourceYIndex + x];
}
}
/// <summary>
/// Copies a block from this ColorBitmap to the given ArgbBitmap.
/// </summary>
public void CopyBlockTo(ArgbBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
{
if (ManagedBitmap.i_ValidateCopyToParameters(this, destinationBitmap, sourceLocation, destinationLocation, blockSize))
return;
var destPixels = destinationBitmap.PixelArray;
for (int y = 0; y < blockSize.Height; y++)
{
int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
int destinationYIndex = ((destinationLocation.Y + y) * Width) + destinationLocation.X;
for (int x = 0; x < blockSize.Width; x++)
destPixels[destinationYIndex + x] = PixelArray[sourceYIndex + x].ToArgb();
}
}
/// <summary>
/// Copies a block from this ColorBitmap to the given GrayscaleBitmap.
/// </summary>
public void CopyBlockTo(GrayscaleBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
{
if (ManagedBitmap.i_ValidateCopyToParameters(this, destinationBitmap, sourceLocation, destinationLocation, blockSize))
return;
var destPixels = destinationBitmap.PixelArray;
for (int y = 0; y < blockSize.Height; y++)
{
int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
int destinationYIndex = ((destinationLocation.Y + y) * Width) + destinationLocation.X;
for (int x = 0; x < blockSize.Width; x++)
destPixels[destinationYIndex + x] = PixelArray[sourceYIndex + x].GetGrayscaleIndex();
}
}
#endregion
#region CloneAsSystemBitmap
/// <summary>
/// Creates a new 32-bit Argb bitmap that is a copy of this bitmap.
/// </summary>
/// <returns>A System.Drawing.Bitmap that is a copy of this bitmap.</returns>
public Bitmap CloneAsSystemBitmap()
{
Bitmap result = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);
CopyBlockTo(result, new Point(), new Point(), new Size(Width, Height));
return result;
}
#endregion
#region CloneAsColorBitmap
/// <summary>
/// Creates a copy of the current bitmap.
/// </summary>
public ColorBitmap CloneAsColorBitmap()
{
Color[] pixelArray = (Color[])PixelArray.Clone();
return new ColorBitmap(pixelArray, Width);
}
#endregion
#region CloneAsArgbBitmap
/// <summary>
/// Creates a copy of this bitmap as an ArgbBitmap.
/// </summary>
public ArgbBitmap CloneAsArgbBitmap()
{
Color[] sourcePixels = PixelArray;
int count = sourcePixels.Length;
int[] destPixels = new int[count];
for (int i=0; i<count; i++)
destPixels[i] = sourcePixels[i].ToArgb();
ArgbBitmap result = new ArgbBitmap();
result.PixelArray = destPixels;
result.Width = Width;
return result;
}
#endregion
#region CloneAsGrayscaleBitmap
/// <summary>
/// Creates a new grayscale bitmap that is a copy of the given colorBitmap.
/// </summary>
public GrayscaleBitmap CloneAsGrayscaleBitmap()
{
Color[] sourcePixels = PixelArray;
int count = sourcePixels.Length;
byte[] destPixels = new byte[count];
for (int i=0; i<count; i++)
destPixels[i] = sourcePixels[i].GetGrayscaleIndex();
GrayscaleBitmap result = new GrayscaleBitmap();
result.PixelArray = destPixels;
result.Width = Width;
return result;
}
#endregion
#endregion
#region IBitmap Members
Color IBitmap.this[int pixelIndex]
{
get
{
return PixelArray[pixelIndex];
}
set
{
PixelArray[pixelIndex] = value;
}
}
Array IBitmap.PixelArray
{
get
{
return PixelArray;
}
}
void IBitmap.CopyBlockTo(IBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
{
if (destinationBitmap == null)
throw new ArgumentNullException("destinationBitmap");
ColorBitmap colorBitmap = destinationBitmap as ColorBitmap;
if (colorBitmap != null)
{
CopyBlockTo(colorBitmap, sourceLocation, destinationLocation, blockSize);
return;
}
ArgbBitmap argbBitmap = destinationBitmap as ArgbBitmap;
if (argbBitmap != null)
{
CopyBlockTo(argbBitmap, sourceLocation, destinationLocation, blockSize);
return;
}
GrayscaleBitmap grayscaleBitmap = destinationBitmap as GrayscaleBitmap;
if (grayscaleBitmap != null)
{
CopyBlockTo(grayscaleBitmap, sourceLocation, destinationLocation, blockSize);
return;
}
throw new ArgumentException("There is no conversion to the given destination bitmap.");
}
#endregion
#region ICloneable Members
object ICloneable.Clone()
{
return CloneAsColorBitmap();
}
#endregion
}
}