Windows Forms: Making a cursor bitmap partially transparent

前端 未结 5 1973
一向
一向 2020-12-16 23:10

I want to use partially transparent images in drag/drop operations. This is all set up and works fine, but the actual transformation to transparency has a weird side effect.

相关标签:
5条回答
  • 2020-12-16 23:34

    When I run your code to modify an image in a picturebox with a background grid image, I get the effect you desired without changing your code. Perhaps your image is being drawn over the top of something that has a dark color...

    0 讨论(0)
  • 2020-12-16 23:37

    GDI+ has a number of problems related to alpha blending when doing interop with GDI (and Win32). In this case, the call to bmp.GetHbitmap() will blend your image with a black background. An article on CodeProject gives more detail on the problem, and a solution that was used for adding images to an image list.

    You should be able to use similar code to get the HBITMAP to use for the mask:

    [DllImport("kernel32.dll")]
    public static extern bool RtlMoveMemory(IntPtr dest, IntPtr source, int dwcount);
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateDIBSection(IntPtr hdc, [In, MarshalAs(UnmanagedType.LPStruct)]BITMAPINFO pbmi, uint iUsage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset);
    
    public static IntPtr GetBlendedHBitmap(Bitmap bitmap)
    {
        BITMAPINFO bitmapInfo = new BITMAPINFO();
        bitmapInfo.biSize = 40;
        bitmapInfo.biBitCount = 32;
        bitmapInfo.biPlanes = 1;
    
        bitmapInfo.biWidth = bitmap.Width;
        bitmapInfo.biHeight = -bitmap.Height;
    
        IntPtr pixelData;
        IntPtr hBitmap = CreateDIBSection(
            IntPtr.Zero, bitmapInfo, 0, out pixelData, IntPtr.Zero, 0);
    
        Rectangle bounds = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
        BitmapData bitmapData = bitmap.LockBits(
            bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb );
        RtlMoveMemory(
            pixelData, bitmapData.Scan0, bitmap.Height * bitmapData.Stride);
    
        bitmap.UnlockBits(bitmapData);
        return hBitmap;
    }
    
    0 讨论(0)
  • 2020-12-16 23:44

    A while ago, I read this problem arises out of a requirement for pre-multiplied alpha channels in the bitmaps. I'm not sure if this was an issue with Windows cursors or GDI, and for the life of me, I cannot find documentation regarding this. So, while this explanation may or may not be correct, the following code does indeed do what you want, using a pre-multiplied alpha channel in the cursor bitmap.

    public class CustomCursor
    {
      // alphaLevel is a value between 0 and 255. For 50% transparency, use 128.
      public Cursor CreateCursorFromBitmap(Bitmap bitmap, byte alphaLevel, Point hotSpot)
      {
        Bitmap cursorBitmap = null;
        External.ICONINFO iconInfo = new External.ICONINFO();
        Rectangle rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    
        try
        {
          // Here, the premultiplied alpha channel is specified
          cursorBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppPArgb);
    
          // I'm assuming the source bitmap can be locked in a 24 bits per pixel format
          BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
          BitmapData cursorBitmapData = cursorBitmap.LockBits(rectangle, ImageLockMode.WriteOnly, cursorBitmap.PixelFormat);
    
          // Use either SafeCopy() or UnsafeCopy() to set the bitmap contents
          SafeCopy(bitmapData, cursorBitmapData, alphaLevel);
          //UnsafeCopy(bitmapData, cursorBitmapData, alphaLevel);
    
          cursorBitmap.UnlockBits(cursorBitmapData);
          bitmap.UnlockBits(bitmapData);
    
          if (!External.GetIconInfo(cursorBitmap.GetHicon(), out iconInfo))
            throw new Exception("GetIconInfo() failed.");
    
          iconInfo.xHotspot = hotSpot.X;
          iconInfo.yHotspot = hotSpot.Y;
          iconInfo.IsIcon = false;
    
          IntPtr cursorPtr = External.CreateIconIndirect(ref iconInfo);
          if (cursorPtr == IntPtr.Zero)
            throw new Exception("CreateIconIndirect() failed.");
    
          return (new Cursor(cursorPtr));
        }
        finally
        {
          if (cursorBitmap != null)
            cursorBitmap.Dispose();
          if (iconInfo.ColorBitmap != IntPtr.Zero)
            External.DeleteObject(iconInfo.ColorBitmap);
          if (iconInfo.MaskBitmap != IntPtr.Zero)
            External.DeleteObject(iconInfo.MaskBitmap);
        }
      }
    
      private void SafeCopy(BitmapData srcData, BitmapData dstData, byte alphaLevel)
      {
        for (int y = 0; y < srcData.Height; y++)
          for (int x = 0; x < srcData.Width; x++)
          {
            byte b = Marshal.ReadByte(srcData.Scan0, y * srcData.Stride + x * 3);
            byte g = Marshal.ReadByte(srcData.Scan0, y * srcData.Stride + x * 3 + 1);
            byte r = Marshal.ReadByte(srcData.Scan0, y * srcData.Stride + x * 3 + 2);
    
            Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4, b);
            Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4 + 1, g);
            Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4 + 2, r);
            Marshal.WriteByte(dstData.Scan0, y * dstData.Stride + x * 4 + 3, alphaLevel);
          }
      }
    
      private unsafe void UnsafeCopy(BitmapData srcData, BitmapData dstData, byte alphaLevel)
      {
        for (int y = 0; y < srcData.Height; y++)
        {
          byte* srcRow = (byte*)srcData.Scan0 + (y * srcData.Stride);
          byte* dstRow = (byte*)dstData.Scan0 + (y * dstData.Stride);
    
          for (int x = 0; x < srcData.Width; x++)
          {
            dstRow[x * 4] = srcRow[x * 3];
            dstRow[x * 4 + 1] = srcRow[x * 3 + 1];
            dstRow[x * 4 + 2] = srcRow[x * 3 + 2];
            dstRow[x * 4 + 3] = alphaLevel;
          }
        }
      }
    }
    

    The pinvoke declarations are found in the External class, shown here:

    public class External
    {
      [StructLayout(LayoutKind.Sequential)]
      public struct ICONINFO
      {
        public bool IsIcon;
        public int xHotspot;
        public int yHotspot;
        public IntPtr MaskBitmap;
        public IntPtr ColorBitmap;
      };
    
      [DllImport("user32.dll")]
      public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);
    
      [DllImport("user32.dll")]
      public static extern IntPtr CreateIconIndirect([In] ref ICONINFO piconinfo);
    
      [DllImport("gdi32.dll")]
      public static extern bool DeleteObject(IntPtr hObject);
    
      [DllImport("gdi32.dll")]
      public static extern IntPtr CreateBitmap(int nWidth, int nHeight, uint cPlanes, uint cBitsPerPel, IntPtr lpvBits);
    }
    

    A few notes on the code:

    1. To use the unsafe method, UnsafeCopy(), you must compile with the /unsafe flag.
    2. The bitmap copying methods are ugly, especially the safe method, which uses Marshal.ReadByte()/Marshal.WriteByte() calls. There must be a faster way to copy bitmap data while also inserting alpha bytes.
    3. I do assume that the source bitmap is able to be locked in a 24 bits per pixel format. This should not be a problem, though.
    0 讨论(0)
  • 2020-12-16 23:50

    try lowering Blue's value to .7 or .6 and see if that is closer to what you want.

    Here's a good site that explains ColorMatrix:

    0 讨论(0)
  • 2020-12-16 23:59

    Forgive me if my suggestion is too simplistic (I'm still new to C#) but I found this on the MSDN site and maybe this might point you in the right direction?

    /matt

    0 讨论(0)
提交回复
热议问题