10

DrawingContext (Visual または DrawingGroup の一部) があり、そこで多数の四角形や 1 ビット イメージを重ねて描画します。マスキング 1 ビット イメージと考えてください。これをビットマップ画像ファイルに変換したいと思います。

RenderTargetBitmap32 ビット ピクセル形式でしかレンダリングできないため、使用はオプションではありません。そのため、20 MB の 1 ビット イメージをレンダリングする必要がある場合、ヒープに 640 MB (20*32) のメモリが必要になります。もちろん、これは壮大な LOH フラグメンテーションを作成し、アプリケーションは 2 回目のショットでメモリ不足になります。

したがって、基本的には、描画コンテキストから 1 ビットのビットマップ ファイルを効率的に書き込む方法が必要です。アイデア/提案/代替方法をいただければ幸いです。

4

3 に答える 3

7

いくつかのアイデア、いくつかは少し複雑です...

XPS に出力してからビットマップを抽出する

Visualを XPS ドキュメントに印刷できます。

運が良ければ、 で描画した 1 ビット イメージが結合さDrawingContextれ、XPS ファイルに 1 つのビットマップが生成されます。

Rectangles の場合、XPS で Rectangles をベクトルベースの情報として保持する場合があります (「Shape」ベースRectangle、またはDrawingContext DrawRectangle両方がこれを行う可能性があります)。コンテキスト。

XPS ファイルを取得したら、それを解析して "画像" を抽出できます (運が良ければ、内部で生成されたものです)。(XPS は、XAML を使用してコンテンツを記述し、サブディレクトリを使用してビットマップ イメージ データ ファイル、フォントなどを格納する単なる ZIP ファイルです)。

Package(クラスを使用して、XPS ドキュメントの生の部分にアクセスしたりXpsDocument、より高いレベルの解釈を行うことができると思います)。

または、結合した 1 ビット イメージを別のコンテキストで表示できるようにする方法を提供するだけの場合は、XPS ビューアーで XPS を表示するだけです。

Print to Image 機能を提供するプリンタ ドライバを使用する

これは理想的ではありません...特に、マシンにプリンタードライバーをインストールする必要があるためです...しかし、「イメージへの印刷」プリンタードライバーを使用して、それを使用してビットマップを作成できる場合があります。以下にいくつかの提案を示します。

GDI+ HBITMAP を作成し、GDI+ 呼び出しを使用して描画を行います...次に、WPF で使用するためにラップします...またはディスクに保存します。

[1] まず、合成レンダリングを保持するのに十分な大きさの GDI+ ビットマップを作成します

(これを行うにはいくつかの方法があります...1つの方法は、WriteableBitmapを使用してバックバッファビット/ストアを提供することです...それは、メモリ内のビットにアクセスしたい場合です...あなたの場合、私はしませんあなたがする/する必要があると思います)。

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

または、WriteableBitmap アクセスが必要な場合は、このようにします。

[2] WPF BitmapSources を GDI+ Bitmaps に変換して、DrawImage を使用して GDI+ Graphics コンテキストに描画できるようにします。

CopyPixels を使用して、BitmapSources でそれを行うことができます。

長方形の場合は、GDI+ DrawRectangle レンダリング コマンドを使用できます。

[3] GDI+ ビットマップをディスクに保存する

System.Image.Bitmap で .Save メソッドを使用して、ビットマップとして保存します。

[4] 保存した画像を Image 要素のソースとして使用する

(1bppであっても、画像をロードするときにおそらく大量のメモリを使用することに注意してください... WPFレンダリングパスはすべて32bppであるため)。

[4] または、WPF で使用するために GDI+ ビットマップをラップします

InteropBitmap を使用して GDI+ ベースのビットマップをラップできるため、WPF で BitmapSource として使用できます。(それは 32bpp でなければならないかもしれないことに注意してください....そうしなければならない場合は...それから正方形 1 に戻ります...とにかく試してみてください)。

ビットマップ「サービス」を作成する

結合された 1bpp イメージをレンダリングするために、サービスとして機能する別のプロセスを作成します (NT サービスである必要はありません...開始する子プロセスである可能性があります)。それはレンダリングコマンドです。

メモリ消費が高くなりすぎたり、LOH が断片化したりした場合は、このサービスを再起動できます。

その他のアイデア

OpenGL を使用してレンダリングすることも (OpenTK や SharpGL を使用するなど)、DirectX を使用してレンダリングすることもできます。

LOH ヒープに多くの改良が加えられているため、NET 4.5 を試してみてください。

于 2012-09-03T01:02:23.860 に答える
2

私はあなたが何かより良いことができるとは思わない. RenderTargetBitmap は MILcore を使用しているため、アクセスできません。そして、ビジュアルをコピーする方法は他にないと思います。ただし、もう1つのオプションがあると思います。1行ではありませんが、それで十分だと思います。

基本的にはブロックごとに視覚的なブロック (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;
}

基本的に、BlackWhite フォーマットで新しい WriteableBitmap を設定し、この関数を次のように呼び出します。

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 を HDD に保存します。

于 2012-09-02T07:27:53.840 に答える
0

PixelFormatsについて

編集:(Anders Gustafssonに感謝)

下はPixelFormats.BlackWhiteで、1 ピクセルあたり 1 ビットです。

このようにして、任意の BitmapImage を FormatConvertedBitmap に変換し、フォーマットをより低い bpp に変更できます。

于 2012-08-31T11:20:07.010 に答える