2009-08-06 11 views
8

Desidero utilizzare le immagini parzialmente trasparenti nelle operazioni di trascinamento/rilascio. Tutto questo è impostato e funziona bene, ma l'effettiva trasformazione della trasparenza ha uno strano effetto collaterale. Per qualche motivo, i pixel sembrano essere fusi su uno sfondo nero.Windows Form: creazione di una bitmap del cursore parzialmente trasparente

L'immagine seguente descrive il problema:

Transparency problem

Figura a) è la bitmap originale.

La figura b) è ciò che viene prodotto dopo che è stata eseguita la miscelazione alfa. Ovviamente questo è molto più scuro del previsto filtro alfa al 50%.

Figura c) è l'effetto desiderato, immagine a) con una trasparenza del 50% (aggiunta alla composizione con un programma di disegno).

Il codice che uso per produrre l'immagine trasparente è la seguente:

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; 

Qualcuno sa il motivo per cui l'alpha blending non funziona?

Aggiornamento I:

Per chiarezza, il codice funziona se sto alphablending sulla cima di una superficie disegnato. Il problema è che voglio creare un'immagine completamente semitrasparente da un'immagine esistente e usarla come un cursore dinamico durante le operazioni di trascinamento/rilascio. Anche saltando quanto sopra e dipingendo solo un rettangolo pieno di colore 88ffffff produce un colore grigio scuro. Qualcosa di strano sta succedendo con l'icona.

Aggiornamento II:

Dal momento che ho reseached un bel po 'e che questo ha qualcosa a che fare con la creazione del cursore, sarò comprendono che il codice qui sotto anche. Se I GetPixel-campiona la bitmap appena prima della chiamata CreateIconIndirect, i quattro valori di colore sembrano essere intatti. Quindi ho la sensazione che i colpevoli potrebbero essere i membri hbmColor o hbmMask della struttura IconInfo.

Ecco la struttura 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 qui è il codice che in realtà crea il cursore:

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

Le due funzioni esterne sono definite come 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); 
+0

Non conosco la risposta, ma questa è una domanda molto buona. :) –

+0

Grazie! Sembra davvero * davvero * strano per me che anche dopo quindici anni di operazioni di trascinamento/rilascio e cursori personalizzati, questa cosa non può essere risolta facilmente. Ottengo * qualche * trasparenza così almeno il sistema suggerisce che si possa fare. Dopo tre giorni passati a battere sulla tastiera e a leggere articoli, ho un leggero sospetto che potrebbe essere necessario attingere alla struttura BITMAPV5HEADER. Ecco un articolo in C++ che potrebbe risolvere il problema: http://support.microsoft.com/default.aspx?scid=kb;en-us;318876. Se qualcuno può portarlo, sarò lieto di consegnare il voto di risposta accettato. – Pedery

risposta

6

GDI + ha un certo numero di problemi legati alla alpha blending quando facendo interoperabilità con GDI (e Win32). In questo caso, la chiamata a bmp.GetHbitmap() fonderà l'immagine con uno sfondo nero. Un article on CodeProject fornisce maggiori dettagli sul problema e una soluzione che è stata utilizzata per aggiungere immagini a un elenco di immagini.

Si dovrebbe essere in grado di utilizzare codice simile per ottenere il HBITMAP da utilizzare per la maschera:

[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

Ho provato il tuo codice e ha funzionato! Molte grazie! Tuttavia, c'è una cosa qui che è * parzialmente * sbagliata. La struttura BITMAPINFO è in realtà una struttura BITMAPINFOHEADER. Il primo contiene un BITMAPINFOHEADER come primo membro del valore, quindi entrambi funzionerebbero in pratica. Non sono sicuro che il tuo approccio o l'approccio gestito/non gestito di Tarser qui sotto sia il migliore, ma prima hai risposto e ottenuto la risposta accettata. – Pedery

0

prova ad abbassare il valore di Blue a .7 o .6 e vedere se è più vicino a quello che vuoi.

Ecco un buon sito che spiega ColorMatrix:

+4

Negativo. Il tuo suggerimento cambierebbe semplicemente i colori originali, quindi applichi l'alpha blending. Come accennato in precedenza, credo che questo abbia qualcosa a che fare con l'alfa dell'immagine che in qualche modo si fonde con uno sfondo nero. Nella mia applicazione alpha 0 produce un'immagine trasparente al 100% e alfa completo restituisce l'immagine originale (con le parti trasparenti superiore e inferiore ancora in fase di separazione). – Pedery

+1

@Pedery: +1 per utilizzare la parola "Negativo". : O) – AMissico

0

Quando eseguo il codice per modificare un'immagine in una PictureBox un'immagine di sfondo griglia con, ottengo l'effetto desiderato senza modificare il codice. Forse la tua immagine viene disegnata sopra a qualcosa che ha un colore scuro ...

+0

Come ho detto, questo codice ha lo scopo di creare un'immagine semitrasparente per le operazioni di trascinamento/rilascio. Quindi l'immagine non dovrebbe essere disegnata sopra nulla, ma diventare parte di un cursore. Forse succede per ragioni come i pixel sottostanti hanno valori 0x00000000, ma ho provato a cambiarlo in 0x00FFFFFF senza alcun risultato. – Pedery

0

Perdonami se il mio suggerimento è troppo semplicistico (sono ancora nuovo in C#) ma l'ho trovato sul sito MSDN e forse this potrebbe indicarti la giusta direzione?

/opaco

+0

Ciao Matt! Grazie per il suggerimento, ma il problema non è creare una bitmap trasparente di per sé. Il problema è piuttosto che quando la bitmap viene convertita in un'icona, la parte di trasparenza sembra essere sovrapposta a nero opaco prima del rendering. Questo potrebbe essere un effetto collaterale per CreateIconIndirect. – Pedery

3

Qualche tempo fa, ho letto questo problema nasce da un requisito per i canali alfa pre-moltiplicato nella bitmap. Non sono sicuro se si è trattato di un problema con i cursori di Windows o GDI e, per la vita di me, non riesco a trovare la documentazione relativa. Quindi, sebbene questa spiegazione possa o meno essere corretta, il seguente codice fa effettivamente quello che vuoi, usando un canale alfa pre-moltiplicato nella bitmap del cursore.

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

Il PInvoke dichiarazioni si trovano nella classe esterna, mostrata qui:

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

Alcune note sul codice:

  1. di utilizzare il metodo non sicuro, UnsafeCopy(), devi compilare con il flag/non sicuro.
  2. I metodi di copia bitmap sono brutti, in particolare il metodo sicuro, che utilizza le chiamate Marshal.ReadByte()/Marshal.WriteByte(). Ci deve essere un modo più veloce per copiare i dati bitmap mentre si inseriscono anche byte alfa.
  3. Suppongo che la bitmap di origine sia in grado di essere bloccata in un formato da 24 bit per pixel. Questo non dovrebbe essere un problema, però.
+0

Fantastico! Non vedo l'ora di provarlo domani e di inserirlo nel mio codice. Gazillion grazie! – Pedery

+0

Vorrei utilizzare parte del codice riguardante una classe CustomCursor sull'articolo che sto preparando. Inoltre mi piacerebbe sapere se esistono aggiornamenti riguardanti lo stesso problema. – none

Problemi correlati