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