Преобразование Drawing.Bitmap в Imaging.Metafile - Pixel Perfect - PullRequest
4 голосов
/ 02 октября 2010

Я пытаюсь преобразовать Drawing.Bitmap в Imaging.Metafile для вставки метафайла в Forms.RichTextBox.(Для справки, встраивание растрового изображения в метафайл является рекомендуемой практикой для помещения растрового изображения в формат Richtext (см. Спецификация Rich Text Format (RTF), версия 1.9.1, стр. 149.) К сожалению, это также, кажется, единственный способ встраиванияизображение в Forms.RichTextBox, поскольку я не могу заставить работать любые зависящие от устройства или устройства способы вставки растрового изображения в RichTextBox.)

Позже я должен получить данные пикселей изметафайл.Я требую, чтобы пиксели метафайла точно совпадали с пикселями растрового изображения.Однако когда я выполняю преобразование, пиксели слегка изменяются.(Возможно, из-за GDI Image Color Management (ICM)?)

Вот мой метод:

public static Imaging.Metafile BitmapToMetafileViaGraphicsDrawImage(Forms.RichTextBox rtfBox, Drawing.Bitmap bitmap)
{
    Imaging.Metafile metafile;
    using (IO.MemoryStream stream = new IO.MemoryStream())
    using (Drawing.Graphics rtfBoxGraphics = rtfBox.CreateGraphics())
    {
        IntPtr pDeviceContext = rtfBoxGraphics.GetHdc();

        metafile = new Imaging.Metafile(stream, pDeviceContext);
        using (Drawing.Graphics imageGraphics = Drawing.Graphics.FromImage(metafile))
        {
            //imageGraphics.DrawImage(bitmap, new Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height));
            imageGraphics.DrawImageUnscaled(bitmap, new Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height));
        }
        rtfBoxGraphics.ReleaseHdc(pDeviceContext);
    }
    return metafile;
}

В этом случае я получаю доступ к пикселям метафайла следующим образом:

metafile.Save(stream, Imaging.ImageFormat.Png);
Bitmap bitmap = new Bitmap(stream, false);
bitmap.GetPixel(x, y);

Я также безуспешно пытался использовать технику BitBlt.

Техника BitBlt:

[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
static extern int BitBlt(
  IntPtr hdcDest,     // handle to destination DC (device context)
  int nXDest,         // x-coord of destination upper-left corner
  int nYDest,         // y-coord of destination upper-left corner
  int nWidth,         // width of destination rectangle
  int nHeight,        // height of destination rectangle
  IntPtr hdcSrc,      // handle to source DC
  int nXSrc,          // x-coordinate of source upper-left corner
  int nYSrc,          // y-coordinate of source upper-left corner
  System.Int32 dwRop  // raster operation code
  );

public static Imaging.Metafile BitmapToMetafileViaBitBlt(Forms.RichTextBox rtfBox, Drawing.Bitmap bitmap)
{
    const int SrcCopy = 0xcc0020;

    Graphics bitmapGraphics = Graphics.FromImage(bitmap);
    IntPtr pBitmapDeviceContext = bitmapGraphics.GetHdc();

    RectangleF rect = new RectangleF(new PointF(0, 0), new SizeF(bitmap.Width, bitmap.Height));
    Imaging.Metafile metafile = new Imaging.Metafile(pBitmapDeviceContext, rect);
    Graphics metafileGraphics = Graphics.FromImage(metafile);
    IntPtr metafileDeviceContext = metafileGraphics.GetHdc();

    BitBlt(pBitmapDeviceContext, 0, 0, bitmap.Width, bitmap.Height, 
        metafileDeviceContext, 0, 0, SrcCopy);

    return metafile;
}

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

IntPtr h = metafile.GetHenhmetafile(); // ArgumentException "Parameter is not valid."
byte[] data;
uint size = GetEnhMetaFileBits(h, 0, out data);
data = new byte[size];
GetEnhMetaFileBits(h, size, out data);
stream = new IO.MemoryStream(data);

Как преобразовать растровое изображение в метафайл без изменения пикселей, а затем позже получить данные пикселей снова?Спасибо!


Установка разрешения растрового изображения

Вот как я пытаюсь установить разрешение растрового изображения так, чтобы разрешение метафайла соответствовало:

Drawing.Bitmap bitmap = new Drawing.Bitmap(width, height, 
Imaging.PixelFormat.Format32bppArgb); // Use 32-bit pixels so that each component (ARGB) matches up with a byte
// Try setting the resolution to see if that helps with conversion to/from metafiles
Drawing.Graphics rtfGraphics = rtfBox.CreateGraphics();
bitmap.SetResolution(rtfGraphics.DpiX, rtfGraphics.DpiY);

// Set the pixel data
...

// Return the bitmap
return bitmap;

rtfBoxтот же самый, отправленный BitmapToMetafileViaGraphicsDrawImage.

Ответы [ 4 ]

5 голосов
/ 04 октября 2010

Вы можете создавать и обрабатывать метафайл вручную (например, перечислять его записи), в этом случае вы можете фактически вставить растровое изображение с его точными данными в поток метафайла.Однако это не так просто, как может показаться.

При воспроизведении метафайла GDI все операции внутренне преобразуются в GDI +, который на самом деле является совершенно другим API и который по-разному обрабатывает многие вещи.К сожалению, встроенный способ сделать это, как только метафайл имеет некоторую сложность или необходимо преобразовать выходные данные, - позволить GDI визуализировать метафайл на растровом изображении с низким разрешением, а затем нарисовать его, что никогда не даст вам результатов, которые выищу.

Для проекта (с закрытым исходным кодом - так что не спрашивайте источник;)) Мне пришлось реализовать полный механизм воспроизведения GDI-to-GDI +, аналогичный функциональности EMFExplorer , но с использованием только управляемого кода и повторного выравнивания одного символа для точного текстового вывода.Это означает лишь то, что это можно сделать, если вы готовы потратить некоторое время на работу со всеми нужными вам записями метафайлов (которые должны быть только небольшим подмножеством, но все же).


Отредактируйте, чтобы ответить на вопросы, заданные в комментариях:

Метафайл - это не что иное, как набор инструкций рисования, в основном запись выполненных операций GDI (+).Таким образом, вы должны иметь возможность создавать метафайл в виде двоичных данных, состоящих в основном только из заголовка и растрового изображения, и в этом случае (поскольку вы не проходите сквозные операции, а всегда обрабатываете растровое изображение на двоичном уровне), вы сможетечтобы получить те же самые данные из метафайла, который вы в него записали.

К счастью, уже несколько лет Microsoft документирует и делает свои форматы файлов и протоколы общедоступными, чтобы получить точную документацию WMF./ Доступен формат файла EMF.Формат метафайла объясняется MS здесь: http://msdn.microsoft.com/en-us/library/cc230514(PROT.10).aspx

Здесь описана его структура: http://msdn.microsoft.com/en-us/library/cc230516(v=PROT.10).aspx

Здесь описаны растровые записи: http://msdn.microsoft.com/en-us/library/cc231160(v=PROT.10).aspx

Использованиеэта информация, вы должны быть в состоянии собрать двоичные данные (возможно, используя BinaryWriter ), а затем также загрузить / перечислить файл, чтобы получить данные обратно (например, найти растровое изображение снова в метафайле и извлечь необходимыеданные из него).

2 голосов
/ 05 октября 2010

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

Установите разрешение растрового изображения, в которое вы конвертируете обратно, с разрешением вашего метафайла.

0 голосов
/ 05 октября 2010

это забавный способ решить эту проблему, но если все, что вам нужно, это поместить его в RichTextBox - вы можете использовать буфер обмена:

    private void button1_Click(object sender, EventArgs e)
    {
        System.Drawing.Bitmap bmp = new Bitmap("g.bmp");
        Clipboard.SetData(DataFormats.Bitmap, bmp);

        richTextBox1.Paste();
    }

но я не уверен в обратном (чтение растрового изображения из текста RTF)

0 голосов
/ 04 октября 2010

Это просто выстрел в темноте, но посмотрите на некоторые значения флага ImageFlags и посмотрите, могут ли они быть включены в генерацию или рендеринг метафайла.

...