How do I create a dynamic mouse cursor .NET without using interop?

前端 未结 4 991
有刺的猬
有刺的猬 2021-01-20 08:01

I have an application which I\'m interested in eventually porting to mono so I\'m trying to avoid using p/invoke\'s to accomplish this task.

I would like to load a c

4条回答
  •  谎友^
    谎友^ (楼主)
    2021-01-20 08:19

    The code below creates a stream filled with data in .cur format, which I succesfully tested in Ubuntu with Mono. However .NET without using Win32 API only supports black and white colors in custom cursor:

    The Cursor class does not support animated cursors (.ani files) or cursors with colors other than black and white.

    Therefore in Unix with Mono one is rewarded with a cursor which

    • displays black and white colors only
    • has no semi-transparent pixels, only fully opaque / fully transparent ones

    ¯\_(ツ)_/¯

    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace StackoverflowExample
    {
        public static class CursorHelper
        {
            public static Cursor CreateCursor(Bitmap bmp, Point hotSpot)
            {
                // https://en.wikipedia.org/wiki/ICO_(file_format)
                var bmpData = bmp.LockBits(new Rectangle(default, bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    
                try
                {
                    int numBytes = bmpData.Stride * bmpData.Height;
                    var bgraValues = new byte[numBytes];
                    Marshal.Copy(bmpData.Scan0, bgraValues, 0, numBytes);
    
                    int max = Math.Max(bmp.Width, bmp.Height);
    
                    if (max > 256)
                        throw new NotSupportedException();
    
                    byte iconSizeByte = _sizes.FirstOrDefault(s => s >= max); // 0 means 256
                    int iconSizeI = iconSizeByte == 0 ? 256 : iconSizeByte;
                    const int bytesPerPixel = 4;
                    const int bytesPerPixelSource = 4;
                    byte[] emptyPixel = new byte[bytesPerPixel];
    
                    using (var stream = new MemoryStream())
                    using (var writer = new BinaryWriter(stream))
                    {
                        writer.Write((ushort)0); // idReserved
                        writer.Write((ushort)2); // idType, 1 = .ico 2 = .cur
                        writer.Write((ushort)1); // idCount
    
                        writer.Write(iconSizeByte);
                        writer.Write(iconSizeByte);
                        writer.Write((byte)0); // colorCount
                        writer.Write((byte)0); // reserved
                        writer.Write((ushort)hotSpot.X);
                        writer.Write((ushort)hotSpot.Y);
    
                        var pixelsCount = iconSizeI * iconSizeI;
                        var xorLength = pixelsCount * bytesPerPixel;
                        var andLength = pixelsCount / 8 * 2;
    
                        writer.Write((uint)(40 + xorLength + andLength)); // sizeInBytes
                        writer.Write((uint)stream.Position + sizeof(uint)); // fileOffset = 22 = 0x16
    
                        writer.Write(40u); // cursorInfoHeader.biSize
                        writer.Write((int)iconSizeI); // cursorInfoHeader.biWidth
                        writer.Write((int)iconSizeI * 2); // cursorInfoHeader.biHeight
                        writer.Write((ushort)1); // cursorInfoHeader.biPlanes
                        writer.Write((ushort)(8 * bytesPerPixel)); // cursorInfoHeader.biBitCount
                        writer.Write(0u); // cursorInfoHeader.biCompression
                        writer.Write(0u); // cursorInfoHeader.biSizeImage
                        writer.Write(0); // cursorInfoHeader.biXPelsPerMeter;
                        writer.Write(0); // cursorInfoHeader.biYPelsPerMeter;
                        writer.Write(0u); // cursorInfoHeader.biClrUsed = binaryReader2.ReadUInt32();
                        writer.Write(0u); // cursorInfoHeader.biClrImportant = binaryReader2.ReadUInt32();
    
                        using (var andMask = new MemoryStream(andLength))
                        {
                            byte def = 255;
    
                            for (int j = 0; j < iconSizeI; j++)
                            {
                                int y = iconSizeI - 1 - j;
                                byte curByte = def;
    
                                for (int i = 0; i < iconSizeI; i++)
                                {
                                    var bitIndex = 7 - i % 8;
    
                                    if (i < bmp.Width && y < bmp.Height)
                                    {
                                        var p = y * bmpData.Stride + i * bytesPerPixelSource;
                                        stream.Write(bgraValues, p, bytesPerPixel);
    
                                        if (bgraValues[p + 3] > 0)
                                            curByte = (byte)(curByte & ~(1 << bitIndex));
                                    }
                                    else
                                        stream.Write(emptyPixel, 0, emptyPixel.Length);
    
                                    if (bitIndex == 0)
                                    {
                                        andMask.WriteByte(curByte);
                                        curByte = def;
                                    }
                                }
                            }
    
                            for (int j = 0; j < iconSizeI; j++)
                                for (int b = 0; b < iconSizeI / 8; b++)
                                    andMask.WriteByte(def);
    
                            andMask.Seek(0, SeekOrigin.Begin);
                            andMask.CopyTo(stream);
                        }
    
                        stream.Seek(0, SeekOrigin.Begin);
    
                        // debug
                        // File.WriteAllBytes("/home/kolia/Documents/stream", stream.ToArray());
                        // stream.Seek(0, SeekOrigin.Begin);
    
                        var cursor = new Cursor(stream);
                        return cursor;
                    }
                }
                finally
                {
                    bmp.UnlockBits(bmpData);
                }
            }
    
            private static readonly byte[] _sizes = { 16, 32, 64, 128 };
        }
    }
    

提交回复
热议问题