2010-10-02 194 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