2010-05-09 16 views
6

Ho bisogno di aiuto per impostare un'immagine trasparente negli Appunti. Continuo a ricevere "handle non valido". Fondamentalmente, ho bisogno di un "secondo set di occhi" per esaminare il seguente codice. (Il progetto di lavoro completo a ftp://missico.net/ImageVisualizer.zip.)Serve aiuto per impostare un'immagine con sfondo trasparente negli Appunti

Questa è una libreria di classi Debug Visualizer di immagine, ma ho reso il progetto incluso da eseguire come eseguibile per il test. (Si noti che la finestra è una finestra degli strumenti e mostra nella barra delle applicazioni è impostata su false.) Ero stanco di dover eseguire una cattura della schermata nella finestra degli strumenti, aprire la cattura dello schermo con un editor di immagini e quindi eliminare lo sfondo aggiunto perché era una cattura dello schermo. Così ho pensato di mettere rapidamente l'immagine trasparente negli appunti. Bene, il problema è ... nessun supporto per la trasparenza per Clipboard.SetImage. Google in soccorso ... non del tutto.

Questo è quello che ho finora. Ho tirato da un certo numero di fonti. Vedi il codice per il riferimento principale. Il mio problema è "handle non valido" quando si utilizza CF_DIBV5. Devo usare BITMAPV5HEADER e CreateDIBitmap?

Qualsiasi aiuto da voi GDI/GDI + Wizards sarebbe molto apprezzato.

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

Puoi chiarire quale passaggio ti dà l'errore di handle non valido? –

risposta

0

Bitmap.GetHbitmap() in realtà comporterà la bitmap su uno sfondo opaco, perdendo il canale alfa. This question ha spiegato come ottenere un HBITMAP con il canale alfa intatto.

+0

Lo stesso problema di "handle non valido". Inoltre, se utilizzo CF_BITMAP lo sfondo è Nero anziché "controllo grigio" utilizzato dal controllo PictureBox. – AMissico

+0

La documentazione per i formati degli appunti (vedere http://msdn.microsoft.com/en-us/library/ms649013.aspx) afferma che quando si utilizza CF_DIB, l'handle deve essere un buffer allocato utilizzando GlobalAlloc, che contiene un BITMAPINFO struttura, seguita dai bit bitmap. Mi aspetterei che dovresti eseguire questo manualmente manualmente. Quando si utilizza CF_BITMAP, è possibile passare un HBITMAP, ma non ci sono i metadati associati per specificare l'uso del canale alfa, quindi viene ignorato. –

1

vedo tre problemi:

  1. L'errore di handle non valido potrebbe venire dal lasciare memBM selezionato in memDC. Dovresti sempre selezionare la bitmap da una DC prima di passarla da un'altra parte.

  2. BitBlt è una chiamata GDI (non GDI +). GDI non conserva il canale alfa. Nelle versioni più recenti di Windows, è possibile utilizzare AlphaBlend per comporre una bitmap con alpha su uno sfondo, ma il composito non avrà un canale alfa.

  3. Hai creato una bitmap compatibile, il che significa che la bitmap ha lo stesso formato di colore della DC che hai passato (che presumo sia la stessa dello schermo). Quindi la tua bitmap potrebbe finire come 16 bit, 24 bit, 32 bit o anche 8 bit, a seconda di come è impostato lo schermo. Se BitBlt avesse conservato il canale alfa dell'originale, è probabile che perderlo durante la conversione in formato schermo.

+1

AlphaBlend è in circolazione da un po 'di tempo. –

4

ci sono alcune cose che potete fare per rafforzare la base di codice un po ', e ho fatto un certo lavoro sul CF_DIBV5 che si possono trovare utili.

Prima di tutto, in __copyMenuItem_Click(), abbiamo quattro copie complete dell'immagine, che è molto più del necessario.

  1. __pictureBox.Image
  2. Image imag = Image.FromStream(ms);
  3. new Bitmap(imag)
  4. Bitmap img = new Bitmap(new Bitmap(imag)); (bitmap esterno)

Inoltre, il vostro MemoryStream, imag, new Bitmap(imag), e img non ottengono disposti, che potrebbe tradursi in i problemi.

Senza cambiare l'intento del codice (e, probabilmente, senza risolvere il problema maniglia), si potrebbe riscrivere il metodo come questo:

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 prossima cosa che sembrava che sarebbe richiedono l'immediata era la linea:

IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM); 

sono abbastanza certo che si deve schierare la struttura BITMAPV5HEADER di passare bit negli appunti quando si utilizza CF_DIBV5. Ho sbagliato in precedenza, ma vorrei verificare che memBM contenga effettivamente l'intestazione. Un buon indicatore è se il primo DWORD (UInt32) ha il valore 124 (la dimensione dell'intestazione in byte).

Le mie osservazioni finali sono più raccomandazioni di un secondo paio di occhi. Ho trovato che le applicazioni fotografiche come GIMP, Paint.NET, Fireworks e PhotoScape sembrano avere un supporto scarso o inesistente per l'incollatura di CF_DIBV5 (Format17). si potrebbe considerare di copiare negli appunti il ​​formato PNG, con una bitmap opaca come backup nel caso in cui l'applicazione di destinazione non supporti PNG. Io uso un metodo di estensione per facilitare questo:

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 il metodo di estensione in mano, il metodo __copyMenuItem_Click() potrebbe essere ridotto a quanto segue, e il metodo SetClipboardData() potrebbe essere rimosso del tutto:

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

Ora , come già discusso in un'altra discussione, il supporto PNG potrebbe non essere sufficiente per te. Ho testato questo approccio su alcune applicazioni; tuttavia, spetterà a te determinare se questo è un supporto di trasparenza sufficiente per le tue esigenze.

  • GIMP: la trasparenza supportato
  • Fireworks (3.0): la trasparenza supportato
  • PhotoScape: sfondo bianco
  • Paint.NET: sfondo bianco
  • MS PowerPoint 2007: trasparenza supportato
  • MS Word 2007: sfondo bianco
  • MS Paint (Win7): sfondo bianco

Discussione su tutto ciò che ho esaminato sarebbe troppo lungo per Stack Overflow. Ho un ulteriore codice di esempio e discussione disponibile sul mio blog: http://www.notesoncode.com/articles/2010/08/16/HandlingTransparentImagesOnTheClipboardIsForSomeReasonHard.aspx

Buona fortuna!

+0

Non ho tempo per provare il tuo codice, ma ti concedo la generosità per tutto il tuo duro lavoro. Il codice postato è un codice throw-away che ho raccolto da numerose fonti al fine di ottenere qualsiasi cosa per funzionare. Quindi il tuo "secondo set di occhi" e le raccomandazioni sono di grande aiuto. Non appena avrò raggiunto, proverò i tuoi suggerimenti. – AMissico

+0

Ho scoperto che molte app (ab) usano DIB v1 a 32 bit (intestazione 40 byte) di tipo "bitfields" (compression = 3) come ARGB, mentre secondo le sue specifiche è solo RGB. Tra questi, lo schermo stampato di Windows 10 (che imposta i bit "alpha" nello screenshot a 255 ovunque tranne che nella parte inesistente della mia configurazione a doppio monitor) e Google Chrome. – Nyerguds