[VBNET] 關於 MetaFile

這篇整理最近回覆網友的文章,相關文章可以查詢:

原發問者自己分的很散,分在下面三篇:
 
我做簡單的整理,就是原提問者在 VB2005 下用 Graphics 類別畫了個命盤,希望能順利插入 Word ,並可縮放。
跟 MetaFile 無關的我就跳掉,只整理跟 MetaFile 相關的部份。

Graphics 畫出來的只有兩種,一種是向量檔 (EMF),一種是點陣檔,點陣檔你不特別處理的往 Word 送話,會採用預設 bitmap 格式,反而造成檔案肥大,這樣是否妥切?(建議用 EMF 較佳)
 
建立 Graphics 後,當然用 Graphics.DrawString 來寫字。
GDI+ 只有包 API 裡面的 TextOut ,沒有包 DrawText ,所以更多的需求要自己去呼叫 API ,一般來說,DrawString 已經夠用了。
另外那篇有引用 回覆:關於繪圖  http://forums.microsoft.com/msdn-cht/ShowPost.aspx?PostID=1297399&SiteID=14
這篇裡面的圖,至少座標軸都有標文字,當然可以加文字。
使用 Graphics 時,不管是直接畫在 bitmap 還是 metafile ,都會受世界座標轉換影響,你自己看看,你是不是自己把座標定錯,畫到奇怪的位置了,metafile 不指定圖面範圍時,你所有畫的東西都會顯示在 emf 檔中,指定圖面範圍時才會裁剪。
這篇:動態文字轉圖檔範例 http://tlcheng.spaces.live.com/blog/cns!145419920BFD55A7!397.entry
跟試用位置:http://tlcheng.twbbs.org/aspx/Tools/Count/FontEnum.aspx
裡面就有輸出為 emf 的範例,直接在 bitmap / emf 上寫文字,引用其中一段程式碼:


Select Case LCase(strExtName)
   Case "emf", "wmf"
      Dim emf As New cMetaFile
      emf.CreatePictureGraphics()
      grfx = emf.MyCreateMetaFileGraphics()
      pic = emf
   Case Else
      bmp = New System.Drawing.Bitmap(boxWidth, boxHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
      grfx = System.Drawing.Graphics.FromImage(bmp)
      pic = bmp
End Select

With grfx
   If Len(strBackgroundColor) > 0 Then
      .FillRectangle(bgColor, 0, 0, CSng(boxWidth), CSng(boxHeight))
   End If
   .DrawString(strNumber, axFont, axColor, ptf, strfmt)
   .Dispose()
End With


可看到 metafile / bmp 只是建立 Graphics 的來源不同,建立完成後,用同樣的 Graphics 填底色、畫字串,就完成文字轉圖檔了。  

高品質的縮圖都是採用混色運算,所以會把鄰近的顏色都混在一起,若改低品質的抽點運算,對於文字、線條則可能會發生掉線的情形。
最好的方法是輸出成向量檔,則會自動重繪,若不會輸出向量檔,標準作法是在每次 Paint 事件中重繪。
你可以參考前面關於 Paint 的相關討論:
http://forums.microsoft.com/MSDN-CHT/Search/Search.aspx?words=PAINT&localechoice=31748&SiteID=14&searchscope=allforums

在這篇:[VBNET] 如何支援 EMZ/WMZ

在 VBNET 下使用 MetaFile ,不需要管到 GetHdc、IntPtr ,除非你有 GDI+ 以外的需求,你可先從線上手冊的範例開始學習,若不夠你用,有一本這方面的聖經巨作 (中譯本):
Petzold, Microsoft Windows 程式設計-使用Visual Basic .Net
雖然這本是 VB2002 的書,不過觀念上及程式碼是互通,可以直接參考。
第 23 章整章討論 MetaFile 的建立,共 42 頁,另一篇回覆你的也說了,Bitmap 與 Metafile 只差在 Graphics 建立來源,所以本書其他章節的內容都可應用在 MetaFile 。當然,再 Windows API 下來看也是一樣,兩個只是建出來的 hDC 不同,其他針對 hDC 的動作都是一樣的。
http://www.kingsinfo.com.tw/item_detail.asp?pro_id=516
http://www.microsoft.com/mspress/taiwan/books/book20758.htm
最新的一本是改用 WPF 實做本書的內容,前一本則是用 WinAPI 實做相同的內容。
主要討論的內容為視窗繪圖(含印表機,印表機只是另一個 DC),附帶討論鍵盤、滑鼠,但不討論控制項,跟一般 VBNET 入門書完全是不同取向,全書共計 1,415 頁,仍只把整個視窗繪圖說了個大概,但已比一般書籍幾頁帶過或是一章帶過,完整多了,屬於字典級的叢書,是全球公認的聖經級巨作。

建立非實體的 EMF 可先宣告一個 MemoryStream 給 MetaFile 用即可。
線上手冊以 Stream 建立 MetaFile 的都是:
http://msdn2.microsoft.com/zh-tw/library/system.drawing.imaging.metafile.metafile(VS.80).aspx
以 MetaFile MemoryStream 為關鍵字就可以找到一堆範例:
http://www.google.com.tw/custom?domains=tlcheng.twbbs.org%3Btlcheng.spaces.live.com&q=MetaFile+MemoryStream&sa=Google+%E6%90%9C%E5%B0%8B&sitesearch=&client=pub-0932425128722654&forid=1&ie=utf-8&oe=utf-8&cof=GALT%3A%23008000%3BGL%3A1%3BDIV%3A%23CAF99B%3BVLC%3A663399%3BAH%3Acenter%3BBGC%3AFFFFFF%3BLBGC%3AFFFFFF%3BALC%3A0000FF%3BLC%3A0000FF%3BT%3A000000%3BGFNT%3A0000FF%3BGIMP%3A0000FF%3BLH%3A30%3BLW%3A123%3BL%3Ahttp%3A%2F%2Ftlcheng.twbbs.org%2FComImage%2Flogobar_s.png%3BS%3Ahttp%3A%2F%2Ftlcheng.twbbs.org%2Fwwwmap.htm%3BFORID%3A1&hl=zh-TW

我所做的縮放程式碼使用:
Code Snippet
If dScale = 1 Then
     picImage.SizeMode = PictureBoxSizeMode.AutoSize
Else
     picImage.SizeMode = PictureBoxSizeMode.Normal
     picImage.Width = showImage.Width * dScale
     picImage.Height = showImage.Height * dScale
     picImage.SizeMode = PictureBoxSizeMode.StretchImage
End If
dScale 為縮放比例。
picImage 對應你 PictureBox1。
showImage 對應你的 m 。

請參考 MetaFile 建構函式關於 New MetaFile(Stream) 就是不需要 hDC 的作法。
hDC 是參考解析度,如果你建構函式省略 hDC ,則預設採用螢幕解析度,除非你打算參照印表機解析度,否則通常可省略。 
螢幕解析度約在 72 ~ 96 DPI ,印表機解析度起跳就是 200 DPI ,例如傳真印表機,目前實體印表機解析度多高達 1200 DPI 以上,但要參照印表機解析度時,需有該印表機驅動程式。
理論上可以自定解析度,不過我沒試過。
你直接在引數內使用 g.GetHdc ,則屬於自動釋放,會在 Windows 閒置時自動釋放,或程式結束時自動釋放,強制立即釋放回收資源可在變數生命週期外,呼叫 GC.Collect() 。

以下都是進一步的知識,是否要深入,就看個人選擇,說明只是為了讓這篇可以完整的結束。


WMF vs EMF
wmf 是 Windows 3.x (Win16) 內建的向量格式,Win32 改用 emf 。

主要差異在於 wmf 採用 2 bytes Integer 儲存座標,emf 採用 4 bytes 儲存座標,所以解析度本身就差很多。
標準 wmf 欠缺圖紙、圖面等關係,所以實際尺寸並不能對應到圖檔中,Office 4.2/4.3 有小改版 wmf 格式,稱為 wmf(p) ,在圖檔前方加上 22 bytes 來處理這個問題,但是並不直接被 Windows API 所支援。
EMF+
在 .Net 新增 emf+ 功能,若你需要高解析度時,兩種作法,一種參照較高解析度的印表機,達成座標轉換,一種使用 SizeF/PointF 等有帶 F 字尾的實數座標,讓 .Net 自動幫你轉換,所以實數座標在螢幕上或點陣檔作用不大,因為還要進位回整數座標,但實數座標對於向量檔效果滿大的。
當你使用到實數座標時,就是由 GDI+ 支援的 emf+ ,只使用整數座標,則是 emf 。
解析度
此外,由於 emf 可以自動縮小,所以早期另一種作法是畫很大的區域,在令其縮小,也就是說手動換算解析度。
比如說你原先座標為 0 ~ 100 ,則全部乘上 100 倍,則你的解析度就是 7200 ~ 9600 DPI 。當你顯示在螢幕上你會看不出效益(註:但字型縮放較平緩),但列印到印表機就會略有差異。(註:主要是點陣式及噴墨印表機會有差,繪圖機、雷射印表機採向量式列印,不受影響,這些以前我都評估過,所以正常使用下,我多半參照印表機解析度來處理)
其它軟體支援 
若你要結合前述複製貼上的方式時,最好還是回頭改用 emf+ ,否則有可能貼到 Office 文件下時,會是一個極大圖框。在 Office 中,不論是插入圖檔或是貼上 emf 時,都會依據 emf 檔頭描述的資訊來顯示圖檔大小,Word 可能依據頁寬自動縮小,則顯示出對應原始大小的百分比,所以要考慮到交換檔案時,最好座標系改用 cm/mm 等有對應實體大小的解析度,不要使用 Pixel 會依據 DC 不同而改變。

Office 中,Word / Visio / Publisher 軟體是參照印表機的 hDC ,其他如 Excel / PowerPoint 等則參照螢幕的 hDC 。
所以早期開啟 Word 時,若是沒有安裝印表機,Word 會跳出訊息框說找不到印表機,一般建議是至少安裝 Windows 內附的 Fax 虛擬印表機,可達 200 DPI 給 Word 參照用,Office 2003 則附贈 Microsoft Office Document Image Writer ,可達 300 DPI 。而 Visio / Publisher 若找不到印表機時,則會自動改參照螢幕的 hDC ,所以我自建的類別主要就是處理這段,未指定時,自動去參照印表機解析度,若沒安裝印表機時,則改參照螢幕解析度。 
一般解析度不同,也就是說設定不同印表機時,會發現自動換行可能稍微不同,這就是解析度差異的些微影響,但是仰賴 Word 排版時,可能排好的文章受到不同印表機影響,造成自動排版變化,比如說圖檔被擠到下一頁等問題,就只能先列印到虛擬印表機,才不會受列印裝置影響,最常見的解決方案就是用 pdf ,Office 2003 / Live Meeting 2005 以後就可以用 Microsoft Office Document Image Writer ,IE7 及 Vista 則提供新版的 Microsoft XPS Document Writer ,不過解析度仍以 pdf 較佳。 

2007/10/11 補充:
回覆:http://forums.microsoft.com/MSDN-CHT/ShowPost.aspx?PostID=2217460&SiteID=14

你不是 EMF 格式的 C.emf 應該是 png 格式。建議你用支援分辨格式的軟體解開來看看。

也有可能是一個 EMF 崁入一個 BMP 圖檔,可以用 Visio / PowerPoint 崁入後,解開群組,若能解開群組,就是一個點陣檔崁入向量檔。

我不知道你指的是哪段程式碼,因為那篇有滿多段落的,假設是最後一個回應時的程式碼:
Dim BitMap1 As New System.Drawing.Imaging.Metafile(stmMemory, ipHdc)

這段已經指出產生 Metafile 了。

使用上我通常用 MemoryStream ,這樣要直接輸出或是存成檔案都滿方便的,畢竟一個 EMF 通常都在 10 MB 以內,用 MemoryStream 暫存 EMF 不會太吃資源。

.Net 是包裝 Windows API 的,所以可以回頭去看 Windows API ,EMF / WMF 只有差在建立 hDC 時,其他都一樣,所以回過頭來看 Metafile 的建構函式,只有兩個:
Metafile 建構函式 (IntPtr, WmfPlaceableFileHeader)
Metafile 建構函式 (IntPtr, WmfPlaceableFileHeader, Boolean)

但是 WmfPlaceableFileHeader 額外檔頭的 WMF 是 Office 4.2 以後加入的特殊格式,不被 Windows API 直接支援。

而 WMF 是 EMF 舊版,由於座標為 2 bytes 整數,亦即表示在 600 DPI 解析度下,最大長寬為 32768 / 600 / 2.54 = 21.5 cm ,一張 A4 約是 29 cm * 21 cm ,表示連一張 A4 大小都不到,所以我根本不考慮 WMF 格式,因為沒意義,要是碰上更高解析度的印表機,例如 1200 DPI 的,只剩下 10 cm 。

我自己沒有特別處理 WMF ,.Net 直接另存 WMF 其實是 EMF ,用一些工具軟體也能看出來,然後去看 Metafile 建構函式,幾乎都是 EMF ,所以我自己是認為 .Net 內建類別新建、輸出只能處理 EMF ,WMF 應該是要自己呼叫 API 建立 hMF 的 DC,否則都是偽的。直接讀取 WMF 另存 WMF 才有可能,因為沒轉換到 EMF 。

我自己在做這方面主要都是為了列印,所以達不到列印需求的 WMF 我在 .Net 並沒有很花時間在研究,以前在 VB5/6 則是直接呼叫 API 來處理,呼叫 API 就只差在起始 hMF 的建立: 

EMF:
hMF = CreateEnhMetaFile(…)

WMF:
hMF = CreateMetaFile(…)

而且那時候好的雷射印表及最高也不過是 600 DPI (HP LaserJet 4/5/6),所以那時才會支援 WMF 。

將 EMF 另存為 WMF 在 API 上就更麻煩,要把一個個繪圖動作列舉出來,然後將 4 bytes 整數轉成 2 bytes 整數,還有部分繪圖動作在 WMF 上不支援,我自己沒有需求的前提下,沒打算搞這個。

Categories: 技術分享 | 1 則迴響

文章導覽

One thought on “[VBNET] 關於 MetaFile

  1. 通告: 擷取 Office 檔內圖片工具 | 鄭子璉

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 變更 )

Twitter picture

You are commenting using your Twitter account. Log Out / 變更 )

Facebook照片

You are commenting using your Facebook account. Log Out / 變更 )

Google+ photo

You are commenting using your Google+ account. Log Out / 變更 )

連結到 %s

在 WordPress.com 建立免費網站或網誌.

%d 位部落客按了讚: