2010-05-09 14 views
6

Necesito ayuda para configurar una imagen transparente en el portapapeles. Sigo recibiendo "el mango no es válido". Básicamente, necesito un "segundo par de ojos" para mirar el siguiente código. (El proyecto de trabajo completo en ftp://missico.net/ImageVisualizer.zip.)Necesita ayuda Configuración de una imagen con fondo transparente en el Portapapeles

Esta es una biblioteca de clases de Debug Visualizer de imagen, pero hice que el proyecto incluido se ejecutara como un ejecutable para la prueba. (Tenga en cuenta que ventana es una ventana de caja de herramientas y mostrar en barra de tareas está configurado en falso.) Estaba cansado de tener que realizar una captura de pantalla en la ventana de caja de herramientas, abrir la captura de pantalla con un editor de imagen y luego eliminar el fondo agregado porque fue una captura de pantalla. Así que pensé que rápidamente pondría la imagen transparente en el portapapeles. Bueno, el problema es ... no hay soporte de transparencia para Clipboard.SetImage. Google al rescate ... no del todo.

Esto es lo que tengo hasta ahora. Saqué de varias fuentes. Vea el código para la referencia principal. Mi problema es el "identificador no válido" cuando uso CF_DIBV5. ¿Debo usar BITMAPV5HEADER y CreateDIBitmap?

Cualquier ayuda de su parte GDI/GDI + Wizards sería muy apreciada.

public static void SetClipboardData(Bitmap bitmap, IntPtr hDC) { 

     const uint SRCCOPY = 0x00CC0020; 
     const int CF_DIBV5 = 17; 
     const int CF_BITMAP = 2; 

     //'reference 
     //'http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/816a35f6-9530-442b-9647-e856602cc0e2 

     IntPtr memDC = CreateCompatibleDC(hDC); 
     IntPtr memBM = CreateCompatibleBitmap(hDC, bitmap.Width, bitmap.Height); 

     SelectObject(memDC, memBM); 

     using (Graphics g = Graphics.FromImage(bitmap)) { 

      IntPtr hBitmapDC = g.GetHdc(); 
      IntPtr hBitmap = bitmap.GetHbitmap(); 

      SelectObject(hBitmapDC, hBitmap); 

      BitBlt(memDC, 0, 0, bitmap.Width, bitmap.Height, hBitmapDC, 0, 0, SRCCOPY); 

      if (!OpenClipboard(IntPtr.Zero)) { 
       throw new System.Runtime.InteropServices.ExternalException("Could not open Clipboard", new Win32Exception()); 
      } 

      if (!EmptyClipboard()) { 
       throw new System.Runtime.InteropServices.ExternalException("Unable to empty Clipboard", new Win32Exception()); 
      } 

      //IntPtr hClipboard = SetClipboardData(CF_BITMAP, memBM); //works but image is not transparent 

      //all my attempts result in SetClipboardData returning hClipboard = IntPtr.Zero 
      IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM); 


      //because 
      if (hClipboard == IntPtr.Zero) { 

       // InnerException: System.ComponentModel.Win32Exception 
       //   Message="The handle is invalid" 
       //   ErrorCode=-2147467259 
       //   NativeErrorCode=6 
       //   InnerException: 

       throw new System.Runtime.InteropServices.ExternalException("Could not put data on Clipboard", new Win32Exception()); 
      } 

      if (!CloseClipboard()) { 
       throw new System.Runtime.InteropServices.ExternalException("Could not close Clipboard", new Win32Exception()); 
      } 

      g.ReleaseHdc(hBitmapDC); 

     } 

    } 

    private void __copyMenuItem_Click(object sender, EventArgs e) { 

     using (Graphics g = __pictureBox.CreateGraphics()) { 

      IntPtr hDC = g.GetHdc(); 

      MemoryStream ms = new MemoryStream(); 

      __pictureBox.Image.Save(ms, ImageFormat.Png); 

      ms.Seek(0, SeekOrigin.Begin); 

      Image imag = Image.FromStream(ms); 

      // Derive BitMap object using Image instance, so that you can avoid the issue 
      //"a graphics object cannot be created from an image that has an indexed pixel format" 

      Bitmap img = new Bitmap(new Bitmap(imag)); 

      SetClipboardData(img, hDC); 

      g.ReleaseHdc(); 

     } 

    } 
+0

¿Puede aclarar qué paso le da el error de manejo no válido? –

Respuesta

0

Bitmap.GetHbitmap() realmente compuesto el mapa de bits a un fondo opaco, perdiendo el canal alfa. This question se dirigió a cómo obtener un HBITMAP con el canal alfa intacto.

+0

Mismo problema de "identificador no válido". Además, si uso CF_BITMAP, el fondo es negro en lugar de "control gris" utilizado por el control PictureBox. – AMissico

+0

La documentación para los formatos del portapapeles (consulte http://msdn.microsoft.com/en-us/library/ms649013.aspx) indica que cuando se utiliza CF_DIB, el identificador se debe asignar mediante GlobalAlloc, que contiene un BITMAPINFO estructura, seguido de los bits del mapa de bits. Esperaría que tuvieras que ordenar esto manualmente. Al usar CF_BITMAP, puede pasar un HBITMAP, pero no hay los metadatos asociados para especificar el uso del canal alfa, por lo que se ignora. –

1

Veo tres problemas:

  1. El error de identificador no válido podría venir de dejar memBM seleccionada en memDC. Siempre debe seleccionar el mapa de bits de un DC antes de pasarlo a otro lugar.

  2. BitBlt es una llamada GDI (no GDI +). GDI no conserva el canal alfa. En las versiones más nuevas de Windows, puede usar AlphaBlend para componer un mapa de bits con alfa sobre un fondo, pero el compuesto no tendrá un canal alfa.

  3. Has creado un mapa de bits compatible, lo que significa que el mapa de bits tiene el mismo formato de color que el DC que pasaste (que supongo que es igual a la pantalla). De modo que su mapa de bits podría terminar en 16 bits, 24 bits, 32 bits o incluso 8 bits, dependiendo de cómo esté configurada la pantalla. Si BitBlt hubiera conservado el canal alfa del original, es probable que lo pierda al convertirlo al formato de pantalla.

+1

AlphaBlend ha existido por bastante tiempo. –

4

Hay algunas cosas que usted puede hacer para endurecer el código base un poco, y he hecho un poco de tarea en CF_DIBV5 que le puede ser útil.

En primer lugar, en __copyMenuItem_Click(), tenemos cuatro copias completas de su imagen, que es mucho más de lo necesario.

  1. __pictureBox.Image
  2. Image imag = Image.FromStream(ms);
  3. new Bitmap(imag)
  4. Bitmap img = new Bitmap(new Bitmap(imag)); (el mapa de bits externa)

Además, su MemoryStream, imag, new Bitmap(imag), y img no obtienen dispuesto, que podría resultar en problemas.

Sin cambiar la intención del código (y probablemente sin resolver el problema de mango), se podría reescribir el método como este:

private void __copyMenuItem_Click(object sender, EventArgs e) 
{ 
    var image = __pictureBox.Image; 
    using (var g = __pictureBox.CreateGraphics()) 
    using (var bmp = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb)) 
    using (var bmpg = Graphics.FromImage(bmp)) 
    { 
     IntPtr hDC = g.GetHdc(); 
     bmpg.DrawImage(image, 0, 0, image.Width, image.Height); 
     SetClipboardData(bmp, hDC); 
     g.ReleaseHdc(); 
    } 
} 

La siguiente cosa que parecia que requieren atención fue la línea:

IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM); 

estoy bastante seguro de que debe reunir la estructura BITMAPV5HEADER para pasar los bits en el portapapeles cuando se utiliza CF_DIBV5. Me he equivocado antes, pero verificaría que memBM en realidad contiene el encabezado. Un buen indicador es si el primer DWORD (UInt32) tiene el valor 124 (el tamaño del encabezado en bytes).

Mis observaciones finales son más una recomendación que un segundo par de ojos. Descubrí que las aplicaciones fotográficas como GIMP, Paint.NET, Fireworks y PhotoScape parecen tener poca o ninguna compatibilidad con CF_DIBV5 (Format17) para pegar. puede considerar copiar en el portapapeles el formato PNG, con un mapa de bits opaco como respaldo, en caso de que la aplicación de destino no sea compatible con PNG. Yo uso un método de extensión para facilitar esto:

public static void CopyMultiFormatBitmapToClipboard(this Image image) 
{ 
    using (var opaque = image.CreateOpaqueBitmap(Color.White)) 
    using (var stream = new MemoryStream()) 
    { 
     image.Save(stream, ImageFormat.Png); 

     Clipboard.Clear(); 
     var data = new DataObject(); 
     data.SetData(DataFormats.Bitmap, true, opaque); 
     data.SetData("PNG", true, stream); 
     Clipboard.SetDataObject(data, true); 
    } 
} 

Con el método de extensión en la mano, su método de __copyMenuItem_Click() podría reducirse a la siguiente, y el método SetClipboardData() podría ser eliminado por completo:

private void __copyMenuItem_Click(object sender, EventArgs e) 
{ 
    __pictureBox.Image.CopyMultiFormatBitmapToClipboard(); 
} 

Ahora , como ya comentamos en otro hilo, el soporte PNG puede no serlo. He probado este enfoque en algunas aplicaciones; sin embargo, dependerá de usted determinar si esto es suficiente soporte de transparencia para sus requisitos.

  • GIMP: la transparencia apoyado
  • fuegos artificiales (3.0): la transparencia apoyado
  • PhotoScape: fondo blanco
  • Paint.NET: fondo blanco
  • MS PowerPoint 2007: la transparencia apoyado
  • MS Word 2007: fondo blanco
  • MS Paint (Windows 7): fondo blanco

La discusión de todo lo que busqué sería demasiado larga para Stack Overflow. Tengo código de ejemplo y discusión adicional disponible en mi blog: http://www.notesoncode.com/articles/2010/08/16/HandlingTransparentImagesOnTheClipboardIsForSomeReasonHard.aspx

Buena suerte!

+0

No tengo tiempo para probar tu código, pero te doy la recompensa por todo tu arduo trabajo. El código publicado es un código desechable que reuní de numerosas fuentes para que todo funcione. Entonces, su "segundo par de ojos" y recomendaciones es de gran ayuda. Tan pronto como me atrape, probaré tus sugerencias. – AMissico

+0

Descubrí que muchas aplicaciones (ab) usan DIB de 32 bits v1 (encabezado de 40 bytes) de tipo "bitfields" (compresión = 3) como ARGB, mientras que según sus especificaciones solo es RGB. Entre ellos, la pantalla de impresión de Windows 10 (estableciendo los bits 'alfa' en la captura de pantalla en 255 en todas partes, excepto en la parte inexistente de la configuración de doble monitor) y Google Chrome. – Nyerguds

Cuestiones relacionadas