2009-08-06 9 views
8

Ich möchte teilweise transparente Bilder in Drag/Drop-Operationen verwenden. Dies ist alles eingerichtet und funktioniert gut, aber die eigentliche Transformation zu Transparenz hat eine seltsame Nebenwirkung. Aus irgendeinem Grund scheinen die Pixel vor einem schwarzen Hintergrund zu liegen.Windows Forms: Eine Cursor-Bitmap teilweise transparent machen

Das folgende Bild beschreibt, das Problem:

Transparency problem

Abbildung a) das ursprüngliche Bitmap ist.

Abbildung b) ist, was nach dem Alpha-Blending erzeugt wurde. Offensichtlich ist dies viel dunkler als der beabsichtigte 50% Alpha-Filter.

Abbildung c) ist der gewünschte Effekt, Bild a) mit 50% Transparenz (mit einem Zeichenprogramm der Komposition hinzugefügt).

Der Code, den ich die trasparent Bild zu erzeugen, verwenden ist folgende:

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; 

Weiß jemand, warum das Alpha-Blending nicht funktioniert?

Update I:

Aus Gründen der Klarheit wird der Code funktioniert, wenn ich auf einer gezogenen Oberfläche Alphablending. Das Problem ist, dass ich ein vollständig semitransparentes Bild aus einem vorhandenen Bild erstellen und dieses als dynamischen Cursor während Drag & Drop-Operationen verwenden möchte. Selbst wenn man das obige überspringt und nur ein gefülltes Rechteck der Farbe 88ffffff malt, erhält man eine dunkelgraue Farbe. Etwas Fischiges geht mit dem Icon vor sich.

Update II:

Da ich eine ganze Menge reseached haben und glauben, dass dies hat etwas mit dem Cursor Schöpfung zu tun, ich werde schließen, dass Code unten zu. Wenn ich GetPixel die Bitmap kurz vor dem CreateIconIndirect-Aufruf sample, scheinen die vier Farbwerte intakt zu sein. So habe ich das Gefühl, die Täter könnten die hbmColor- oder die hbmMask-Mitglieder der IconInfo-Struktur sein.

Hier ist die ICONINFO Struktur:

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. 

} 

Und hier ist der Code, der den Cursor tatsächlich erstellt:

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

Die beiden externen Funktionen sind wie folgt definiert:

[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

ich weiß nicht die Antwort, aber das ist eine _very_ gute Frage. :) –

+0

Danke! Es erscheint mir * wirklich * komisch, dass selbst nach fünfzehn Jahren Drag/Drop-Operationen und benutzerdefinierten Cursorn diese Sache nicht einfach gelöst werden kann. Ich bekomme * einige * Transparenz, so dass zumindest das System darauf hinweist, dass es möglich ist. Nachdem ich drei Tage lang hart auf meine Tastatur geklopft und Artikel gelesen habe, habe ich einen kleinen Verdacht, den man vielleicht braucht, um die BITMAPV5HEADER-Struktur anzuzapfen. Hier ist ein C++ - Artikel, der das Problem lösen könnte: http://support.microsoft.com/default.aspx?scid=kb;en-us;318876. Wenn jemand dies portieren kann, werde ich gerne die angenommene Antwort abgeben. – Pedery

Antwort

6

GDI + hat eine Reihe von Problemen auf Alpha-Blending Zusammenhang, wenn Interop mit GDI zu tun (und Win32). In diesem Fall mischt der Aufruf von bmp.GetHbitmap() Ihr Bild mit einem schwarzen Hintergrund. Eine article on CodeProject gibt weitere Details zu dem Problem und eine Lösung, die zum Hinzufügen von Bildern zu einer Bilderliste verwendet wurde.

Sie sollten in der Lage sein, einen ähnlichen Code zu verwenden, um die HBITMAP zu bekommen für die Maske zu verwenden:

[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

Ich habe deinen Code ausprobiert und es hat funktioniert! Danke vielmals! Es gibt jedoch eine Sache, die * teilweise * falsch ist. Die BITMAPINFO-Struktur ist wirklich eine BITMAPINFOHEADER-Struktur. Ersteres enthält einen BITMAPINFOHEADER als erstes Wertelement, so dass beide in der Praxis funktionieren würden. Ich bin mir nicht sicher, ob Ihr Ansatz oder der von Tarsier gesteuerte/unmanaged Ansatz der bessere ist, aber Sie haben zuerst geantwortet und die akzeptierte Antwort erhalten. – Pedery

0

Versuchen Sie, den Wert von Blue auf .7 oder .6 zu verringern und sehen Sie, ob das näher an Ihren Vorstellungen liegt.

Hier ist eine gute Website, die ColorMatrix erklärt:

+4

Negativ. Ihr Vorschlag würde einfach die ursprünglichen Farben ändern und dann Alpha Blending anwenden. Wie oben erwähnt, glaube ich, dass dies etwas damit zu tun hat, dass das Alpha des Bildes irgendwie vor einem schwarzen Hintergrund gemischt wird. In meiner Anwendung liefert Alpha 0 ein 100% transparentes Bild und volles Alpha gibt das Originalbild zurück (wobei die oberen und unteren transparenten Teile immer noch transparent sind). – Pedery

+1

@Pedery: +1 für die Verwendung des Wortes "Negatory". : O) – AMissico

0

Wenn ich Ihren Code ausführen ein Bild in einem picturebox mit einem Hintergrundgitterbild zu ändern, erhalte ich die Wirkung, die Sie ohne den Code zu ändern gewünscht. Vielleicht wird Ihr Bild über etwas von einer dunklen Farbe gezogen ...

+0

Wie gesagt, dieser Code soll ein halbtransparentes Bild für Drag/Drop-Operationen erzeugen. Daher sollte das Bild nicht über irgendetwas gezeichnet werden, sondern Teil eines Cursors werden. Vielleicht passiert es aus Gründen wie die zugrunde liegenden Pixel haben Werte 0x00000000, aber ich habe versucht, es zu 0x00FFFFFF ohne Erfolg zu ändern. – Pedery

0

Verzeihen Sie mir, wenn mein Vorschlag zu einfach ist (ich bin noch neu in C#), aber ich fand das auf der MSDN-Website und vielleicht this könnte Sie in die richtige Richtung zeigen?

/matt

+0

Hallo Matt! Danke für den Vorschlag, aber das Problem besteht nicht darin, eine transparente Bitmap per se zu erstellen. Das Problem ist eher, dass, wenn die Bitmap in ein Icon umgewandelt wird, der Transparenzteil vor dem Rendern schwarz überlagert zu sein scheint. Dies könnte ein Nebeneffekt von CreateIconIndirect sein. – Pedery

3

Vor einiger Zeit las ich dieses Problem für Pre-multiplizierten Alpha-Kanäle in der aus einer Anforderung entsteht Bitmaps. Ich bin nicht sicher, ob dies ein Problem mit Windows-Cursors oder GDI war, und für mein Leben kann ich keine Dokumentation darüber finden. Während diese Erklärung richtig ist oder nicht, macht der folgende Code tatsächlich das, was Sie wollen, indem Sie einen vormultiplizierten Alphakanal in der Cursor-Bitmap verwenden.

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

Die pinvoke Erklärungen in der externen Klasse zu finden sind, hier gezeigt:

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

ein paar Noten auf dem Code:

  1. die unsichere Methode zu verwenden, UnsafeCopy(), Sie müssen mit dem Flag// unsicher kompilieren.
  2. Die Bitmap-Kopiermethoden sind hässlich, vor allem die sichere Methode, die Marshal.ReadByte()/Marshal.WriteByte() Aufrufe verwendet. Es muss einen schnelleren Weg geben, Bitmap-Daten zu kopieren, während auch Alpha-Bytes eingefügt werden.
  3. Ich nehme an, dass die Quell-Bitmap in einem 24 Bit pro Pixel-Format gesperrt werden kann. Dies sollte jedoch kein Problem sein.
+0

Super! Ich freue mich darauf, dies morgen zu testen und es in meinen Code einzubetten. Gazillion danke! – Pedery

+0

Ich möchte einen Teil Ihres Codes in Bezug auf eine Klasse CustomCursor auf Artikel verwenden, den ich vorbereite. Ich würde auch gerne wissen, ob Updates zum selben Thema existieren. – none