Windows Forms: Fazendo um bitmap de cursor parcialmente transparente
-
11-09-2019 - |
Pergunta
Eu quero usar imagens parcialmente transparentes em operações de arrastar / soltar. Isto é tudo configurado e funciona bem, mas a transformação real com a transparência tem um efeito colateral estranho. Por alguma razão, os pixels parecem ser misturado contra um fundo preto.
A imagem a seguir descreve o problema:
Figura a) é o mapa de bits originais.
Figura b) é o que é produzido após combinação alfa tenha sido realizada. Obviamente este é um mais escuro muito do que o destinado 50% filtro alfabético pretendido.
Figura c) é o efeito desejado, uma imagem) com transparência% 50 (adicionado à composição com um programa de desenho).
O código que eu uso para produzir a imagem trasparent é o seguinte:
Bitmap bmpNew = new Bitmap(bmpOriginal.Width, bmpOriginal.Height);
Graphics g = Graphics.FromImage(bmpNew);
// Making the bitmap 50% transparent:
float[][] ptsArray ={
new float[] {1, 0, 0, 0, 0}, // Red
new float[] {0, 1, 0, 0, 0}, // Green
new float[] {0, 0, 1, 0, 0}, // Blue
new float[] {0, 0, 0, 0.5f, 0}, // Alpha
new float[] {0, 0, 0, 0, 1} // Brightness
};
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
ImageAttributes imgAttributes = new ImageAttributes();
imgAttributes.SetColorMatrix(clrMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(bmpOriginal, new Rectangle(0, 0, bmpOriginal.Width, bmpOriginal.Height), 0, 0, bmpOriginal.Width, bmpOriginal.Height, GraphicsUnit.Pixel, imgAttributes);
Cursors.Default.Draw(g, new Rectangle(bmpOriginal.Width / 2 - 8, bmpOriginal.Height / 2 - 8, 32, 32));
g.Dispose();
imgAttributes.Dispose();
return bmpNew;
Alguém sabe por que a mistura alfa não trabalho?
Atualização I:
Para maior clareza, o código funciona se eu estou alphablending em cima de uma superfície desenhado. O problema é que eu quero criar uma imagem completamente semitransparente de uma imagem existente e usar isto como um cursor dinâmico durante as operações de arrastar / soltar. Mesmo pular o acima e somente pintar um retângulo preenchido de cor 88ffffff produz uma cor cinza escuro. Algo suspeito está acontecendo com o ícone.
Atualização II:
Desde que eu reseached um lote inteiro e acreditamos que isso tem algo a ver com a criação Cursor, eu vou incluir esse código abaixo também. Se eu GetPixel-provar o bitmap apenas antes da chamada CreateIconIndirect, os quatro valores de cor parecem estar intacto. Assim, eu tenho um sentimento que os culpados pode ser a hbmColor ou os membros hbmMask da estrutura ICONINFO.
Aqui está a estrutura ICONINFO:
public struct IconInfo { // http://msdn.microsoft.com/en-us/library/ms648052(VS.85).aspx
public bool fIcon; // Icon or cursor. True = Icon, False = Cursor
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask; // Specifies the icon bitmask bitmap. If this structure defines a black and white icon,
// this bitmask is formatted so that the upper half is the icon AND bitmask and the lower
// half is the icon XOR bitmask. Under this condition, the height should be an even multiple of two.
// If this structure defines a color icon, this mask only defines the AND bitmask of the icon.
public IntPtr hbmColor; // Handle to the icon color bitmap. This member can be optional if this structure defines a black
// and white icon. The AND bitmask of hbmMask is applied with the SRCAND flag to the destination;
// subsequently, the color bitmap is applied (using XOR) to the destination by using the SRCINVERT flag.
}
E aqui está o código que realmente cria o Cursor:
public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot) {
IconInfo iconInfo = new IconInfo();
GetIconInfo(bmp.GetHicon(), ref iconInfo);
iconInfo.hbmColor = (IntPtr)0;
iconInfo.hbmMask = bmp.GetHbitmap();
iconInfo.xHotspot = xHotSpot;
iconInfo.yHotspot = yHotSpot;
iconInfo.fIcon = false;
return new Cursor(CreateIconIndirect(ref iconInfo));
}
As duas funções externas são definidos como se segue:
[DllImport("user32.dll", EntryPoint = "CreateIconIndirect")]
public static extern IntPtr CreateIconIndirect(ref IconInfo icon);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
Solução
GDI + tem uma série de problemas relacionados com a combinação alfa quando fazendo interoperabilidade com GDI (e Win32). Neste caso, a chamada para bmp.GetHbitmap () vai misturar sua imagem com um fundo preto. Um artigo em CodeProject dá mais pormenores sobre o problema, e uma solução que foi usada para adicionar imagens a uma lista de imagem.
Você deve ser capaz de usar um código semelhante para obter o HBITMAP para uso para a máscara:
[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;
}
Outras dicas
Um tempo atrás, eu li este problema surge de uma necessidade de canais alfa pré-multiplicadas em bitmaps. Eu não tenho certeza se isso foi um problema com cursores do Windows ou GDI, e para a vida de mim, eu não posso encontrar documentação sobre isso. Assim, enquanto essa explicação pode ou não ser correto, o código a seguir, de fato, fazer o que quiser, usando um canal alfa pré-multiplicado no bitmap cursor.
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;
}
}
}
}
As declarações PInvoke são encontrados na classe Externo, mostrado aqui:
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);
}
Algumas notas sobre o código:
- Para usar o método inseguro, UnsafeCopy (), você deve compilar com a bandeira / inseguro.
- O bitmap métodos de cópia é feio, especialmente o método seguro, que utiliza Marshal.ReadByte () / Marshal.WriteByte () chamadas. Deve haver uma maneira mais rápida para copiar dados de bitmap ao mesmo tempo, inserindo alfa bytes.
- Eu supor que o bitmap de origem é capaz de ser trancado em um 24 bits por pixel formato. Isso não deve ser um problema, no entanto.
tente diminuir o valor de azul para 0,7 ou 0,6 e ver se isso é mais perto do que você quer.
Aqui está um bom site que explica ColorMatrix :
Quando eu executar o código para modificar uma imagem em um picturebox uma imagem de grade de fundo com, eu obter o efeito desejado sem alterar seu código. Talvez sua imagem está sendo desenhado por cima de algo que tem uma cor escura ...
Perdoe-me se a minha sugestão é demasiado simplista (eu ainda sou novo para C #), mas eu encontrei este no site do MSDN e talvez este pode apontar na direção certa?
/ mate