DrawingContext to Bitmap file

У меня есть DrawingContext (часть Visual или DrawingGroup), где я рисую группу прямоугольников и / или 1-битных изображений друг на друге. Думайте об этом как маскирующее 1-битное изображение. Я хотел бы преобразовать это в файл растрового изображения.

С помощью RenderTargetBitmap это не вариант, потому что он может отображать только в формате 32-битного пикселя, поэтому, если мне придется рендерить 1-битное изображение размером 20 МБ, в моей памяти останется 640 МБ (20*32) памяти. Это, конечно, создает великолепную фрагментацию LOH, и приложение запускается нехватка памяти на втором кадре.

Итак, мне в основном нужен способ эффективно написать 1-битный файл растрового изображения из контекста рисования. Любые идеи / предложения / альтернативные методы будут оценены.

3 ответа

Ряд идей, некоторые немного запутанные...

Распечатать в XPS, затем извлечь растровое изображение

Вы могли бы распечатать Visual к документу XPS.

Если вам повезет, он объединит 1-битные изображения, которые вы нарисовали в DrawingContext и создать один растровое изображение в файле XPS.

Для прямоугольников он может хранить прямоугольники как векторную информацию в XPS (на основе "формы") Rectangle или же DrawingContextDrawRectangle оба могут сделать это).... если это произойдет, тогда создайте растровое изображение, в которое вы начнете рисовать прямоугольную часть, и потяните это растровое изображение в контекст.

Когда у вас есть XPS-файлы, вы можете проанализировать их и извлечь "изображение", если вам повезет, это то, что он создал внутри. (XPS - это просто ZIP-файл, который использует XAML для описания содержимого и использует подкаталоги для хранения файлов данных растровых изображений, шрифтов и т. Д.).

(Я думаю, что вы можете использовать Package класс для доступа к необработанным частям документа XPS, или XpsDocument для более высокого уровня интерпретации).

Или просто отобразите XPS в средстве просмотра XPS, если вы хотите просто предоставить способ просмотра ваших объединенных 1-битных изображений в другом контексте.

Используйте драйвер принтера, который обеспечивает функцию печати в изображение

Это не идеально... не в последнюю очередь потому, что вам нужно установить драйвер принтера на машину... но вы можете использовать драйвер принтера "Print to Image", а затем использовать его для создания своего растрового изображения. Вот некоторые предложения:

Создайте GDI+ HBITMAP и выполните рисование, используя вызовы GDI +... затем оберните его для использования WPF... или сохраните на диск.

[1] Сначала создайте растровое изображение GDI +, достаточно большое, чтобы содержать ваш составной рендеринг

(есть несколько разных способов сделать это... один из способов - использовать WriteableBitmap для предоставления битовых буферов / хранилища... это если вы хотите получить доступ к битам в памяти... в вашем случае я этого не делаю думаю, что вы / нужно).

var bmp = new System.Drawing.Bitmap( pixelwidth, pixelheight, System.Drawing.Imaging.Format1bppIndexed );

Или так, если вы хотите доступ к WriteableBitmap.

[2] Конвертируйте любые источники растровых изображений WPF в растровые изображения GDI +, чтобы вы могли рисовать их в контексте графики GDI + с помощью DrawImage.

Вы можете использовать CopyPixels, чтобы сделать это на ваших BitmapSources.

Для ваших прямоугольников вы можете просто использовать команду рендеринга GDI+ DrawRectangle.

[3] Сохранить GDI+ Bitmap на диск

Используйте метод.Save в System.Image.Bitmap, чтобы сохранить его как растровое изображение.

[4] Используйте сохраненное изображение в качестве источника для элемента изображения

(обратите внимание, что при загрузке вашего изображения, вероятно, будут использоваться большие объемы памяти, даже если она составляет 1 бит на дюйм..., поскольку путь рендеринга WPF составляет 32 бит на дюйм).

[4] ИЛИ обернуть GDI+ Bitmap для использования в WPF

Вы можете использовать InteropBitmap для переноса растрового изображения на основе GDI +, чтобы вы могли использовать его как BitmapSource в WPF. (обратите внимание, что это может быть 32 бита на секунду... если это должно... тогда вы вернетесь на квадрат 1... но попробуйте в любом случае).

Создать растровое изображение "Сервис"

Создайте другой процесс, который действует как служба (не обязательно должен быть службой NT... это может быть просто дочерний процесс, который вы запускаете) для визуализации ваших объединенных изображений 1bpp.... Существуют различные способы связи с ним, чтобы дать это команды рендеринга.

Когда потребление памяти становится слишком высоким / LOH становится фрагментированным, вы можете перезапустить эту службу.

Другие идеи

Вы можете выполнять рендеринг с использованием OpenGL (например, использовать OpenTK или SharpGL) или рендерить с использованием DirectX... через 3D-путь с помощью D3DImage или Direct2D (независимо от того, работает ли он так же, как RenderTargetBitmap с точки зрения использования памяти... это нужно выяснить).

Попробуйте NET 4.5, поскольку в куче LOH произошел ряд улучшений:

Я не думаю, что вы можете сделать что-нибудь лучше. Так как RenderTargetBitmap использует MILcore, к которому вы не можете получить доступ. И я не думаю, что есть какой-либо другой способ скопировать Visual. Однако я думаю, что есть еще один вариант. Это не будет одна строка, но я думаю, что это будет достаточно хорошо.

В основном вы будете визуализировать визуальный блок за блоком (PGBRA32) и конвертировать его в BlackWhite на лету, а затем объединить его с буфером Blackwhite. Я начал небольшой пример кода, но на полпути решил, что это будет не так просто, но вы можете закончить его.

 /// <summary>
/// Renders only part of the visual and returns byte[] array
/// which holds only black/white information.
/// </summary>
/// <param name="oVisual"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns>black/white pixel information. one pixel=one bit. 1=white, 0=black</returns>
public static byte[] RenderPartially(Visual oVisual, 
    int x, int y, double width, double height)
{
    int nWidth = (int)Math.Ceiling(width);
    int nHeight = (int)Math.Ceiling(height);

    RenderTargetBitmap oTargetBitmap = new RenderTargetBitmap(
        nWidth,
        nHeight,
        96,
        96,
        PixelFormats.Pbgra32
    );

    DrawingVisual oDrawingVisual = new DrawingVisual();
    using (DrawingContext oDrawingContext = oDrawingVisual.RenderOpen())
    {
        VisualBrush oVisualBrush = new VisualBrush(oVisual);

        oDrawingContext.DrawRectangle(
            oVisualBrush,
            null,
            new Rect(
                new Point(x, y),
                new Size(nWidth, nHeight)
            )
        );

        oDrawingContext.Close();
        oTargetBitmap.Render(oDrawingVisual);
    }

    //Pbgra32 == 32 bits ber pixel?!(4bytes)
    // Calculate stride of source and copy it over into new array.
    int bytesPerPixel = oTargetBitmap.Format.BitsPerPixel / 8;
    int stride = oTargetBitmap.PixelWidth * bytesPerPixel;
    byte[] data = new byte[stride * oTargetBitmap.PixelHeight];
    oTargetBitmap.CopyPixels(data, stride, 0);

    //assume pixels in byte[] are stored as PBGRA32 which means that 4 bytes form single PIXEL.
    //so the layout is like:
    // R1, G1, B1, A1,  R2, G2, B2, A2,  R3, G3, B3, A3, and so on.

    byte [] bitBufferBlackWhite = new byte[oTargetBitmap.PixelWidth
        * oTargetBitmap.PixelHeight / bytesPerPixel];

    for(int row = 0; row < oTargetBitmap.PixelHeight; row++)
    {
        for(int col = 0; col < oTargetBitmap.PixelWidth; col++)
        {
            //calculate concrete pixel from PBGRA32
            int index = stride * row + bytesPerPixel * col;
            int r = data[index];
            int g = data[index + 1];
            int b = data[index + 2];
            int transparency = data[index + 3];

            //determine whenever pixel is black or white.
            //note that I dont know the exact process how one converts
            //from PBGRA32 to BlackWhite format. But you should get the idea.
            bool isBlack = r + g + b + transparency == 0;

            //calculate target index and USE bitwise operators in order to
            //set right bits.
        }
    }

    return bitBufferBlackWhite;
}

Итак, по сути, установите новый WriteableBitmap в формате BlackWhite, а затем вызовите эту функцию следующим образом:

WriteableBitmap blackWhiteFullBuffer = new WriteableBItmap(....., PIxelFormats.BlackWhite);
for(int x = 0; x < Visual.Width; x += <some uniform amount that divides correctly>)
{
    for(int y = 0; y < VIsual.Height; y+= <some uniform amount that divides correctly>)
    {
        byte[] partialBuffer = PartialRenderer.RenderPartially(Visual, x, y, <uniform amX>,
          <uniform amY>);
        //concat that partial blackWhite buffer with other blackWhite buffer.
        //you do this as long as needed and memory wont grow too much
        //hopefully it will be fast enough too.
        PartialRenderer.ConcateBuffers(blackWhiteFullBuffer, partialBuffer);
    }
}

// затем сохраняем blackWhiteFullBuffer на жесткий диск, если это необходимо.

Как насчет PixelFormats

Редактировать: (спасибо Андерсу Густафссону)

Ниже PixelFormats.BlackWhite, с 1 бит на пиксель.

Таким образом, вы можете преобразовать любой BitmapImage в FormatConvertedBitmap, где вы можете изменить формат на более низкий bpp.

Другие вопросы по тегам