2010-05-09 2 views
6

Мне нужна помощь в настройке прозрачного изображения в буфер обмена. Я продолжаю получать «дескриптор недействителен». В принципе, мне нужен «второй набор глаз», чтобы просмотреть следующий код. (Полный рабочий проект на ftp://missico.net/ImageVisualizer.zip.)Нужна помощь Настройка изображения с прозрачным фоном в буфер обмена

Это изображение библиотеки классов Debug Visualizer, но я включил проект, который будет запущен как исполняемый файл для тестирования. (Обратите внимание, что окно - это окно панели инструментов, а на панели задач отображается значение false.) Мне было надоело выполнять захват экрана в окне панели инструментов, открывать захват экрана с помощью редактора изображений, а затем удалять добавленный фон, потому что он был захват экрана. Поэтому я решил быстро поместить прозрачное изображение в буфер обмена. Ну, проблема в том, что ... нет поддержки прозрачности для Clipboard.SetImage. Google на помощь ... не совсем.

Это то, что у меня есть до сих пор. Я вытащил из нескольких источников. См. Код для основной ссылки. Моя проблема заключается в «недопустимом дескрипторе» при использовании CF_DIBV5. Нужно ли использовать BITMAPV5HEADER и CreateDIBitmap?

Любая помощь от вас GDI/GDI + Wizards была бы очень благодарна.

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

Можете ли вы уточнить, какой шаг дает вам неправильную ошибку? –

ответ

0

Bitmap.GetHbitmap() фактически будет составлять растровое изображение на непрозрачный фон, теряя альфа-канал. This question рассмотрел вопрос о том, как получить HBITMAP с альфа-каналом.

+0

Такая же проблема с «неправильной ручкой». Более того, если я использую CF_BITMAP, фон будет черным вместо «контрольного серого», используемого элементом управления PictureBox. – AMissico

+0

В документации для форматов буфера обмена (см. Http://msdn.microsoft.com/en-us/library/ms649013.aspx) указано, что при использовании CF_DIB дескриптор должен быть выделен буфером с использованием GlobalAlloc, который содержит BITMAPINFO структуры, за которой следуют битмап-биты. Я бы ожидал, что вам придется вручную выставить это вручную. При использовании CF_BITMAP вы можете передать HBITMAP, но нет связанных метаданных, чтобы указать использование альфа-канала, поэтому он игнорируется. –

1

Я вижу три проблемы:

  1. Инвалид ошибка ручка может исходить от чего memBM выбранного в memDC. Вы всегда должны выбирать растровое изображение из DC, прежде чем передавать его в другом месте.

  2. BitBlt - это вызов GDI (не GDI +). GDI не сохраняет альфа-канал. В новых версиях Windows вы можете использовать AlphaBlend для компоновки растрового изображения с альфой на фоне, но композит не будет иметь альфа-канал.

  3. Вы создали совместимое растровое изображение, что означает, что растровое изображение имеет тот же формат цвета, что и DC, в котором вы проходили (что я предполагаю, совпадает с экраном). Таким образом, ваше растровое изображение может оказаться 16-битным, 24-битным, 32-битным или даже 8-битным, в зависимости от того, как установлен экран. Если BitBlt сохранил альфа-канал оригинала, вы, вероятно, потеряете его при преобразовании в формат экрана.

+1

AlphaBlend существует довольно давно. –

4

Есть несколько вещей, которые вы можете сделать, чтобы подтянуть кодовую немного, и я сделал домашнее задание на CF_DIBV5, которые вы можете найти полезными.

Прежде всего, в __copyMenuItem_Click(), у нас есть четыре полных копии вашего изображения, что намного больше, чем необходимо.

  1. __pictureBox.Image
  2. Image imag = Image.FromStream(ms);
  3. new Bitmap(imag)
  4. Bitmap img = new Bitmap(new Bitmap(imag)); (внешний растровый)

Кроме того, ваш MemoryStream, imag, new Bitmap(imag) и img не получают расположены, что может привести к проблемы.

без изменения намерения кода (и, вероятно, не решая проблему ручки), можно переписать метод так:

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

Следующая вещь, которая выглядела, как это потребовало бы внимания была линия:

IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM); 

Я довольно уверен, что вы должны мобилизовывать из структуры BITMAPV5HEADER передавать биты в буфер обмена при использовании CF_DIBV5. Я ошибался раньше, но я бы проверял, что memBM фактически содержит заголовок. Хорошим индикатором является то, имеет ли первый DWORD (UInt32) значение 124 (размер заголовка в байтах).

Мое последнее замечание - рекомендация, чем вторая пара глаз. Я обнаружил, что приложения для фотографий, такие как GIMP, Paint.NET, Fireworks и PhotoScape, по-видимому, имеют плохую или несуществующую поддержку для вставки CF_DIBV5 (Format17). вы можете рассмотреть возможность копирования в буфер обмена формата PNG с непрозрачным растровым изображением в качестве резервной копии на случай, если целевое приложение не поддерживает PNG. Я использую метод расширения для облегчения этого:

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

С помощью метода расширения в руке, ваш метод __copyMenuItem_Click() может быть сведен к следующему, и методу SetClipboardData() может быть удален полностью:

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

сейчас , как мы уже обсуждали в другом потоке, поддержка PNG может не сократить его для вас. Я тестировал этот подход на нескольких приложениях; однако вам будет необходимо определить, является ли это достаточной прозрачной поддержкой ваших требований.

  • КАНИТЕЛЬ: прозрачность поддерживается
  • фейерверк (3.0): прозрачность поддерживается
  • Photoscape: белый фон
  • Paint.NET: белый фон
  • MS PowerPoint 2007: прозрачность поддерживается
  • MS Word 2007: белый фон
  • MS Paint (Win7): белый фон

Обсуждение всего, что я искал, было бы слишком длинным для переполнения стека. У меня есть дополнительный пример кода и обсуждение в моем блоге: http://www.notesoncode.com/articles/2010/08/16/HandlingTransparentImagesOnTheClipboardIsForSomeReasonHard.aspx

Удачи!

+0

У меня нет времени попробовать свой код, но я даю вам щедрость за всю вашу тяжелую работу. Выложенный код - это код выброса, который я собрал из многочисленных источников, чтобы заставить работать что угодно. Поэтому ваш «второй набор глаз» и рекомендации - большая помощь. Как только я поймаю, я попробую ваши предложения. – AMissico

+0

Я узнал, что многие приложения (ab) используют 32-битный v1 DIB (40-байтовый заголовок) типа «битподы» (сжатие = 3) как ARGB, в то время как по его характеристикам это только RGB. Среди них - Windows 10 printscreen (установка битов «альфа» на скриншоте до 255 везде, кроме несуществующей части моей настройки двойного монитора) и Google Chrome. – Nyerguds