2010-10-02 195 views
4

我试图将Drawing.Bitmap转换为Imaging.Metafile,以便将元文件插入到Forms.RichTextBox中。 (作为参考,将位图嵌入图元文件是将位图置入richtext的推荐做法(请参阅RTF格式(RTF)规范版本1.9.1,第149页)。不幸的是,它似乎也是嵌入一个图像到一个Forms.RichTextBox,因为我无法获取任何设备相关或设备无关的方法将位图插入到RichTextBox中工作)。将Drawing.Bitmap转换为Imaging.Metafile - 像素完美

后来,我必须从元文件中检索像素数据。我需要元文件的像素完全匹配位图的像素。但是,当我执行转换时,像素略有改变。 (?也许是由于GDI图像颜色管理(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。

+0

你是否能够实现你想要的,或者你需要进一步的帮助?如果其中一个答案有帮助,请不要忘记在赏金到期之前接受它(让它过期不会将点返回给您,它们会丢失 - 请参阅http://stackoverflow.com/faq#bounty关于这方面的细节)。 – Lucero 2010-10-10 11:37:56

回答

5

您可以手动创建和处理图元文件(例如枚举其记录),在这种情况下,您实际上可以将具有其确切数据的位图插入到元文件流中。然而,这听起来并不简单。

在播放GDI图元文件时,所有操作都在内部转换为GDI +,它实际上是一个完全不同的API,它处理很多不同的事情。不幸的是,一旦图元文件具有一定的复杂性或需要转换输出,内置的方法就是让GDI在低分辨率位图上渲染图元文件,然后绘制该图元,从而不会给出结果正在寻找。

对于一个项目(封闭源代码 - 所以不要打扰索要源代码;))我必须实现一个类似于EMFExplorer功能的完整的GDI-to-GDI +回放引擎,但只使用托管代码,单个字符重新对齐以获得精确的文本输出。这只是说它可以完成,如果你愿意投入一些时间来处理你需要的所有元文件记录(它应该只是一个小子集,但是仍然)。


编辑,以解决在评论中提出的问题:

图元文件不过是一系列的绘图指令,GDI(+)执行的操作基本上是一个记录。所以你应该能够构造一个元文件作为基本上只包含头部和位图的二进制数据,并且在这种情况下(因为你不通过drwing操作,但总是在二进制级别处理位图),你将能够从您写入的元文件中检索完全相同的数据。

幸运的是,自从几年以来,微软一直在为公众提供文件格式和协议,以便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),然后加载/枚举文件以获取数据(例如,再次在元文件中查找位图并从中提取所需数据)。

+0

您能否详细说明“您实际上可以将具有确切数据的位图插入到元文件流中?”?我会用哪些方法或gdi32调用来执行此操作?另外,“单个字符重新对齐精确文本输出”是什么意思?这是否类似于我尝试在Drawing.Bitmap和Imaging.Metafile之间进行像素完美转换?谢谢。 – 2010-10-05 23:21:56

+0

“内置的方法来做到这一点......在低分辨率位图上渲染图元文件”我认为这可能解释我所看到的错误,因为图元文件的像素转换与两个位图之间的像素转换完全相同,如果我不要在位图构造函数中使用useIcm = false。你能想到解决这个问题的方法吗?注意:为了我的目的,我并不在乎Metafile如何呈现;但是当我在Rich Text中访问它的字节数据时,我要求数据中有完美的像素,以便可以检索在位图像素中编码的数据。谢谢。 – 2010-10-05 23:24:33

+0

我编辑了关于手头问题的答案。由于它是无关紧要的,下面是“单个字符重新排列”问题的答案。即使使用相同的字体和设置,GDI +也会呈现与GDI完全不同的文本。字距不同,字符的确切位置也不同。现在,我必须转换的元文件包含很多文本字符串,并且由于GDI +渲染与文本块的位置不匹配,所以它们未对齐。因此,我实现了逐字符位置计算,使用GDI +的字距调整,但将它们正确定位以适合。 – Lucero 2010-10-06 09:07:17

0

这只是一个黑暗中的镜头,但看看一些ImageFlags标志值,看看它们是否可以合并到图元文件的生成或渲染中。

+0

我看着他们,没有一个人似乎帮助他们的名字。谢谢。 – 2010-10-05 23:25:46

0

这是解决这个问题一种有趣的方式,但如果你需要的是把它里面的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文本的位图)

+1

谢谢,但我不能打扰剪贴板的内容。 – 2010-10-05 22:54:06

+0

剪贴板恐怖!! ......叹! – Nayan 2010-10-07 10:09:29

2

这里有很多可能的答案,两个最有可能的罪魁祸首是别名和解决方案。我的猜测是分辨率,因为当您保存位图而未指定分辨率时,我相信它会自动设置为120 DPI,这可能会影响别名。

在别人评论决议并不重要,并且DPI仅用于印刷和等等之前,请拒绝发表评论的冲动。我很高兴能够就您自己的问题与您进行辩论。

将您要转换的位图的分辨率设置为您的图元文件的分辨率。

+0

你好我已经添加了代码,我设置位图的分辨率。这种方法不幸的工作。我是否正确设置了分辨率(来自RichTextBox.CreateGraphics()。DpiX和.DpiY),使其与元文件匹配?我不确定图元文件如何获得其分辨率,但是我已经发布了所有我的元文件创建代码,所以也许你可以告诉?我假定它从RichTextBox.CreateGraphics()。GetHdc()创建的分辨率。谢谢。 – 2010-10-05 23:14:34

+0

你确定它不是来自ICM吗?我建议这样做的原因是,在测试过程中,我将像素编码为位图,将位图转换为位图(就像稍后我将用元文件做的那样),然后读取像素数据。像素数据在位图之间转换,直到使用位图构造函数的useIcm = false参数为止:newBitmap = new Drawing.Bitmap(oldBitmapStream,false)。在使用useIcm = true的情况下像素的转换与通过Graphics.DrawImage(...)从位图创建图元文件时的转换完全相同。谢谢。 – 2010-10-05 23:18:45

+0

@DGGenuine:我会做一些更多的研究,并让你尽快知道。 – 2010-10-07 03:07:45