using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace Blacklight.Core.UnitTests
{
public class NativeCursorTests : UnitTestSharp.TestFixture
{
// Every channel differs, so a red/blue swap can't pass unnoticed.
public static readonly byte[] Olive = { 0x11, 0x22, 0x33, 0xFF };
public static readonly byte[] Red = { 0xFF, 0x00, 0x00, 0xFF };
public static readonly byte[] Clear = { 0x00, 0x00, 0x00, 0x00 };
public static readonly byte[] HalfBlue = { 0x00, 0x00, 0xFF, 0x80 };
public struct CursorFacts
{
public bool IsIcon;
public Point Hotspot;
public Size Size;
}
public static byte[] Pixels(params byte[][] pixels)
{
var returnMe = new byte[pixels.Length * CursorImage.BytesPerPixel];
for (int i = 0; i < pixels.Length; ++i)
{
Array.Copy(pixels[i], 0, returnMe, i * CursorImage.BytesPerPixel, CursorImage.BytesPerPixel);
}
return returnMe;
}
///
/// Olive and Clear over Red and HalfBlue.
///
public static CursorImage Sample(Point hotspot = default(Point))
{
return new CursorImage(Pixels(Olive, Clear, Red, HalfBlue), new Size(2, 2), hotspot);
}
///
/// The same four pixels in the corner of an otherwise transparent cursor the size Windows reports for one.
///
public static CursorImage SystemSizedSample()
{
Size size = SystemInformation.CursorSize;
var pixels = new byte[size.Width * size.Height * CursorImage.BytesPerPixel];
byte[][] corner = { Olive, Clear, Red, HalfBlue };
for (int i = 0; i < corner.Length; ++i)
{
int at = ((i / 2) * size.Width + (i % 2)) * CursorImage.BytesPerPixel;
Array.Copy(corner[i], 0, pixels, at, CursorImage.BytesPerPixel);
}
return new CursorImage(pixels, size, Point.Empty);
}
///
/// What Windows made of a cursor. Its own size has to be read off the colour bitmap, because Cursor.Size
/// reports the system metric rather than anything about this cursor.
///
public static CursorFacts Describe(Cursor cursor)
{
NativeMethods.IconInfo info;
NativeMethods.GetIconInfo(cursor.Handle, out info);
try
{
using (Image colorBitmap = Image.FromHbitmap(info.ColorBitmap))
{
return new CursorFacts
{
IsIcon = info.IsIcon,
Hotspot = new Point(info.HotspotX, info.HotspotY),
Size = colorBitmap.Size,
};
}
}
finally
{
NativeMethods.DeleteObject(info.ColorBitmap);
NativeMethods.DeleteObject(info.MaskBitmap);
}
}
public class ToBitmapTests : UnitTestSharp.TestFixture
{
public void PutsChannelsInTheOrderGdiReadsThem()
{
using (Bitmap bitmap = NativeCursor.ToBitmap(
new CursorImage(Pixels(Olive), new Size(1, 1), Point.Empty)))
{
CheckEqual(unchecked((int)0xFF112233), bitmap.GetPixel(0, 0).ToArgb());
}
}
public void KeepsTheImageSize()
{
using (Bitmap bitmap = NativeCursor.ToBitmap(Sample()))
{
CheckEqual(new Size(2, 2), bitmap.Size);
}
}
public void KeepsRowsTopDown()
{
using (Bitmap bitmap = NativeCursor.ToBitmap(
new CursorImage(Pixels(Olive, Red), new Size(1, 2), Point.Empty)))
{
CheckEqual(unchecked((int)0xFF112233), bitmap.GetPixel(0, 0).ToArgb());
CheckEqual(unchecked((int)0xFFFF0000), bitmap.GetPixel(0, 1).ToArgb());
}
}
public void KeepsFullTransparency()
{
using (Bitmap bitmap = NativeCursor.ToBitmap(Sample()))
{
CheckEqual(0, bitmap.GetPixel(1, 0).ToArgb());
}
}
public void KeepsPartialAlphaUnpremultiplied()
{
using (Bitmap bitmap = NativeCursor.ToBitmap(Sample()))
{
CheckEqual(unchecked((int)0x800000FF), bitmap.GetPixel(1, 1).ToArgb());
}
}
public void NullImage_Throws()
{
CheckThrow(typeof(ArgumentNullException));
NativeCursor.ToBitmap(null);
}
}
public class CreateTests : UnitTestSharp.TestFixture
{
// Cursor.Draw scales from Cursor.Size, which is the system metric and not this cursor's own size, so
// only a cursor authored at that size lands on the target pixel for pixel.
private static int DrawnOverWhite(int x, int y)
{
CursorImage image = SystemSizedSample();
using (NativeCursor cursor = NativeCursor.Create(image))
using (var target = new Bitmap(image.Size.Width, image.Size.Height, PixelFormat.Format32bppArgb))
using (Graphics graphics = Graphics.FromImage(target))
{
graphics.Clear(System.Drawing.Color.White);
cursor.Cursor.Draw(graphics, new Rectangle(Point.Empty, image.Size));
return target.GetPixel(x, y).ToArgb();
}
}
public void ProducesACursor()
{
using (NativeCursor cursor = NativeCursor.Create(Sample()))
{
CheckNotNull(cursor);
CheckNotNull(cursor.Cursor);
}
}
public void ProducesACursorRatherThanAnIcon()
{
using (NativeCursor cursor = NativeCursor.Create(Sample()))
{
CheckFalse(Describe(cursor.Cursor).IsIcon);
}
}
public void KeepsTheHotspot()
{
using (NativeCursor cursor = NativeCursor.Create(Sample(new Point(1, 0))))
{
CheckEqual(new Point(1, 0), Describe(cursor.Cursor).Hotspot);
}
}
public void KeepsASizeWindowsWouldNotHaveChosen()
{
using (NativeCursor cursor = NativeCursor.Create(Sample()))
{
CheckEqual(new Size(2, 2), Describe(cursor.Cursor).Size);
}
}
public void OpaquePixelsCoverWhatIsUnderThem()
{
CheckEqual(unchecked((int)0xFF112233), DrawnOverWhite(0, 0));
}
public void TransparentPixelsLeaveWhatIsUnderThem()
{
CheckEqual(unchecked((int)0xFFFFFFFF), DrawnOverWhite(1, 0));
}
public void NullImage_Throws()
{
CheckThrow(typeof(ArgumentNullException));
NativeCursor.Create(null);
}
}
public class DisposeTests : UnitTestSharp.TestFixture
{
public void DropsTheCursor()
{
NativeCursor cursor = NativeCursor.Create(Sample());
cursor.Dispose();
CheckNull(cursor.Cursor);
}
public void Twice_IsHarmless()
{
NativeCursor cursor = NativeCursor.Create(Sample());
cursor.Dispose();
cursor.Dispose();
CheckNull(cursor.Cursor);
}
}
}
}