【问题标题】:DrawingContext to Bitmap fileDrawingContext 到位图文件
【发布时间】:2012-07-28 17:56:24
【问题描述】:

我有一个 DrawingContext(Visual 或 DrawingGroup 的一部分),我在其中绘制一堆矩形和/或 1 位图像。把它想象成一个掩蔽的 1 位图像。我想把它转换成位图图像文件。

使用RenderTargetBitmap 不是一个选项,因为它只能以 32 位像素格式渲染,所以如果我必须渲染一个 20MB 的 1 位图像,我的内存最终将是 640MB (20*32)堆。这当然会产生巨大的 LOH 碎片,并且应用程序在第二次拍摄时内存不足。

所以,我基本上需要一种有效地从绘图上下文写入 1 位位图文件的方法。任何想法/建议/替代方法将不胜感激。

【问题讨论】:

    标签: c# .net wpf rendertargetbitmap drawingcontext


    【解决方案1】:

    一些想法,有些有点复杂......

    打印到 XPS 然后提取位图

    您可以将Visual 打印到 XPS 文档中。

    如果幸运的话,它会结合您在DrawingContext 中绘制的 1 位图像,并在 XPS 文件中生成单个位图。

    对于矩形,它可能会在 XPS 中将矩形保留为基于矢量的信息(基于“形状”的 RectangleDrawingContext DrawRectangle 可能都这样做)......如果发生这种情况,然后创建一个位图您在其中绘制矩形部分并将该位图绘制到上下文中。

    一旦你有了 XPS 文件,你就可以解析它并提取“图像”,如果幸运的话,那是它在里面产生的。 (XPS 只是一个 ZIP 文件,它使用 XAML 来描述内容并使用子目录来存储位图图像数据文件、字体等)。

    (我认为您可以使用 Package 类访问 XPS 文档的原始部分,或使用 XpsDocument 进行更高级别的解释)。

    或者,如果您的目的只是为了提供一种方法,让您的组合 1 位图像在不同的上下文中被查看,则只需在 XPS 查看器中显示 XPS。

    使用提供打印到图像功能的打印机驱动程序

    这并不理想......尤其是因为您必须在机器上安装打印机驱动程序......但是您可以使用“打印到图像”打印机驱动程序,然后使用它来生成你的位图。以下是一些建议:

    创建一个 GDI+ HBITMAP 并使用 GDI+ 调用进行绘图...然后将其包装以供 WPF 使用...或保存到磁盘。

    [1] 首先创建一个足够大的 GDI+ 位图以容纳您的合成渲染

    (有几种不同的方法可以做到这一点...一种方法是使用 WriteableBitmap 来提供后备缓冲区位/存储...如果您想访问内存中的位...在您的情况下,我不认为你做/不需要)。

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

    如果您想要 WriteableBitmap 访问权限,也可以这样。

    [2] 将任何 WPF 位图源转换为 GDI+ 位图,以便您可以使用 DrawImage 将它们绘制到 GDI+ 图形上下文中。

    您可以使用 CopyPixels 在您的 BitmapSources 上执行此操作。

    对于您的矩形,您可以使用 GDI+ DrawRectangle 渲染命令。

    [3] 将 GDI+ 位图保存到磁盘

    使用 System.Image.Bitmap 上的 .Save 方法将其保存为位图。

    [4] 使用保存的图像作为图像元素的源

    (注意这可能会在加载图像时使用大量内存,即使它是 1bpp...因为 WPF 渲染路径都是 32bpp)。

    [4] 或包装 GDI+ 位图以在 WPF 中使用

    您可以使用 InteropBitmap 来包装基于 GDI+ 的位图,以便您可以将其用作 WPF 中的 BitmapSource。 (请注意,它可能必须是 32bpp 的……如果必须……那么您将回到第 1 格……但无论如何都要尝试)。

    创建位图“服务”

    创建另一个充当服务的进程(不必是 NT 服务...可以只是您启动的子进程)来呈现您的组合 1bpp 图像...有多种通信方式它给它渲染命令。

    当内存消耗过高/LOH 碎片化时,您可以重新启动此服务。

    其他想法

    您可以使用 OpenGL 进行渲染(例如使用 OpenTK 或 SharpGL),或使用 DirectX 进行渲染...通过 D3DImage 或 Direct2D 的 3D 路径(在内存使用方面这是否与 RenderTargetBitmap 相同...出)。

    试用 NET 4.5,因为 LOH 堆中有许多改进:

    【讨论】:

      【解决方案2】:

      我不认为你可以做得更好。由于 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;
      }
      

      所以本质上,用 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。

      【讨论】:

        【解决方案3】:

        PixelFormats

        编辑:(感谢 Anders Gustafsson)

        低位为PixelFormats.BlackWhite,每像素1bit。

        这样一来,你可以将任何BitmapImage转换为FormatConvertedBitmap,在这里你可以将格式修改为更低的bpp。

        【讨论】:

        • 其实还有一个1位的PixelFormats.BlackWhite值。
        • RenderTargetBitmap 不支持除 PBGRA32 之外的任何其他 PixelFormats。它只是抛出一个 NotSupportedException。我也使用 .net 反射器来确认这一点。这个论点只是为了展示目的。在创建 32 位图像后进行转换不是一种选择,因为它已经消耗了大量内存。 40MB 1 位甚至无法渲染,因为分配将耗尽内存。供参考。我当前的程序确实使用了这种方法,但是就像我在帖子中提到的那样,这会产生 LOH 片段,内存不足。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-18
        • 2012-07-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-16
        • 2011-05-08
        相关资源
        最近更新 更多