大多數(shù) Java 開(kāi)發(fā)人員同意 Swing/AWT 只有一個(gè)方面強(qiáng)于 Eclipse 平臺(tái)的標(biāo)準(zhǔn)窗口小部件工具箱(Standard Widget Toolkit),這就是 Java 2D。直到現(xiàn)在仍然沒(méi)有容易的方法將 Java 2D 的快捷性能與 SWT 用戶(hù)界面組件的更強(qiáng)的可移植性、功能和性能集成到一起,但是這一點(diǎn)就會(huì)改變了。這本文中,向大家展示了在 SWT 組件和 Draw2D 圖形中繪制二維圖像有多容易。
SWT (標(biāo)準(zhǔn)窗口小部件工具箱,Standard Widget Toolkit)是在 Eclipse 平臺(tái)上使用的窗口小部件工具箱。它也可以作為 Swing/AWT 的一個(gè)重要替代產(chǎn)品,用于構(gòu)建任何類(lèi)型的 Java GUI 應(yīng)用程序。隨著 Eclipse 平臺(tái)在過(guò)去兩年里的日趨流行,SWT 已經(jīng)進(jìn)入大家的視線(xiàn),并且最近它已經(jīng)開(kāi)始在一些應(yīng)用程序中取代 Swing/AWT。SWT 的流行源自這樣一個(gè)事實(shí):它是跨平臺(tái)的工具箱,利用了窗口小部件的本性,并有一個(gè)與 Swing 及其他現(xiàn)代工具箱同樣強(qiáng)大的功能。使用 SWT,就不用在可移植性、功能和性能之間做取舍了。
事實(shí)上,Swing/AWT 只在一個(gè)方面明顯強(qiáng)于 SWT,這就是 Java 2D。Java 2D 是一個(gè)強(qiáng)大的 API,是在 JDK 1.2 中引入的。它使 Java 開(kāi)發(fā)人員在 AWT 組件上繪制時(shí)可以使用復(fù)雜的二維變換(平移、旋轉(zhuǎn)、縮放、錯(cuò)切等)。不幸的是,Java 2D 設(shè)計(jì)為只在 AWT 或者 Swing 工具箱上使用,而 SWT 還沒(méi)有提供這種擴(kuò)展的二維能力。因此,許多開(kāi)發(fā)人員發(fā)現(xiàn)他們必須選擇是在 Java 平臺(tái)上使用 Java 2D 還是放棄它令人興奮的功能而使用 SWT。
不過(guò),在本文中您將了解到如何同時(shí)擁有這兩方面的好處。我將展示一個(gè)簡(jiǎn)單的技術(shù),利用該技術(shù)可以在 SWT 組件和 Draw2D 圖像上繪制 Java 2D 圖像。為了理解這個(gè)例子,讀者應(yīng)當(dāng)熟悉 Java 2D、AWT 和 SWT。具有一些 Eclipse 平臺(tái)的 GEF(圖形編輯框架,Graphical Editing Framework)的經(jīng)驗(yàn)也是有幫助的。
屏外圖像技術(shù)
本文展示一種簡(jiǎn)單的技術(shù),利用該技術(shù),您可以用 Java 2D 功能在任何 SWT 窗口小部件或者 Draw2D 圖像上繪制。為了彌補(bǔ) SWT 上缺少 Java 2D 的不足,用一個(gè)屏外(offscreen)AWT 圖像接收 Java 2D 繪制操作,并將它們轉(zhuǎn)換為獨(dú)立于工具箱的像素值。再用另一個(gè)由 SWT 工具箱創(chuàng)建的 屏外圖像將這些像素信息繪制在任何 SWT 組件上。圖 1 顯示了 AWT 屏外圖像轉(zhuǎn)換為 SWT 圖像再繪制在 SWT 窗口小部件上的過(guò)程。
圖 1. 屏外圖像技術(shù)允許在 SWT 上使用 Java 2D
圖 1 中顯示的屏外 AWT 圖像被初始化為透明背景。然后對(duì)屏外圖像的圖形上下文調(diào)用 Java 2D 方法。像所有 AWT 圖像一樣,屏外圖像的圖形上下文自動(dòng)支持 Java 2D。完成了所有特定于 Java 2D 的繪制后,提取 AWT 圖像的像素值并傳送到一個(gè)屏外 SWT 圖像中。然后用工具箱的 GC.drawImage(...) 方法在 SWT 組件上繪制這個(gè) SWT 圖像,就像所有正常圖像一樣。
我將在下面幾節(jié)中完成這個(gè)過(guò)程中的每一步。
創(chuàng)建 AWT 屏外圖像
對(duì)于 AWT 屏外圖像,要使用 java.awt.image.BufferedImage 實(shí)例。BufferedImage 是一個(gè)可以通過(guò)它的 API 訪(fǎng)問(wèn)其像素?cái)?shù)據(jù)的圖像。訪(fǎng)問(wèn)到了圖像的像素值就可以在以后將它轉(zhuǎn)換為 SWT 圖像。
構(gòu)建一個(gè) AWT 緩沖圖像的最簡(jiǎn)單方法是使用構(gòu)造函數(shù) public BufferedImage(int width, int height, int imageType)。前兩個(gè)參數(shù)表明圖像具有的大小。第三個(gè)參數(shù)是指定要?jiǎng)?chuàng)建的圖像 類(lèi)型 的常量。圖像類(lèi)型 —— 可能的值是在類(lèi) BufferedImage 中聲明的常量 TYPE_XXX 之一 —— 表明像素值是如何存儲(chǔ)在圖像中的。在彩色圖像中一般使用以下幾種最重要的圖像類(lèi)型:
TYPE_BYTE_INDEXED:這種圖像類(lèi)型的圖像將使用一個(gè)索引顏色模型。索引顏色模型的意思是圖像中使用的每一種顏色都是在一組顏色中索引的。像素值只包含這個(gè)像素的顏色在顏色模型中的索引。這種類(lèi)型的圖像局限于 256 種顏色 —— 也就是顏色模型的大小。以這種方式存儲(chǔ)像素信息可以很緊湊地表示圖像,因?yàn)槊恳粋€(gè)像素都只用一個(gè)字節(jié)編碼。
TYPE_INT_RGB:
這種類(lèi)型常量表明圖像使用直接顏色模型。直接顏色模型 的意思是每一個(gè)像素的值包含關(guān)于其顏色的完整信息。TYPE_INT_RGB 表明每一個(gè)像素都是用一個(gè)整數(shù)(四字節(jié))編碼的。每一個(gè)像素中編碼的信息是這個(gè)像素所使用的顏色的紅、綠和藍(lán)(RGB)成分。每一種顏色成分都用一個(gè)字節(jié)編碼。因?yàn)檎麄€(gè)像素值是用四個(gè)字節(jié)編碼的,所以有一個(gè)字節(jié)是未使用的。這種圖像的內(nèi)部表示占用的內(nèi)存是使用索引顏色模型的圖像的四倍,但是這種圖像可以使用的顏色數(shù)增加到了 1 百 60 萬(wàn)(256 x 256 x 256)。
TYPE_INT_ARGB:
與 TYPE_INT_RGB 一樣,這種類(lèi)型的圖像使用直接顏色模型并用四個(gè)字節(jié)編碼每一個(gè)像素。不同之處在于,這里用 TYPE_INT_RGB 沒(méi)有使用的第四個(gè)字節(jié)表示像素的透明度,有時(shí)也稱(chēng)為顏色的 alpha 成分。這種類(lèi)型的圖像可以有透明的背景,也可以是半透明的。 為了讓屏外圖像技術(shù)可以工作,需要只將受到 Java 2D 繪制影響的像素傳送到 SWT 組件的表面。為了保證這一點(diǎn),初始圖像必須有透明背景。因此,對(duì)于 AWT 屏外圖像,使用的緩沖圖像類(lèi)型為 TYPE_INT_ARGB。
繪制和提取圖像
這個(gè)過(guò)程的下一步是用 Java 2D 繪制圖像。首先取得它的 Graphics2D 上下文??梢杂梅椒?createGraphics2D() 或者調(diào)用 getGraphics() 做到這一點(diǎn)。在這個(gè)上下文上繪制將會(huì)自動(dòng)修改圖像的像素?cái)?shù)據(jù)。在繪制完成后,可以用方法 getRGB(int startX, int startY, int w, int h, int rgbArray, int offset, int scansize) 容易且高效地提取圖像的像素值。這個(gè)方法可以將圖像中矩形區(qū)域的像素?cái)?shù)據(jù)傳輸?shù)揭粋€(gè)整數(shù)數(shù)組中。getRGB() 方法的參數(shù)如下:
startX, startY 是要提取的區(qū)域左上角圖像的坐標(biāo)
w, h 是要提取的區(qū)域的寬度和高度
rgbArray 是接收像素值的整數(shù)數(shù)組
offset 是數(shù)組中接收第一個(gè)像素值的位置的索引。
scansize 是圖像中相鄰兩行中具有相同行索引的像素的索引偏移值。如果這個(gè)值與要提取的區(qū)域的寬度相同,那么一行的第一個(gè)像素就會(huì)存儲(chǔ)在數(shù)組中前一行最后一個(gè)像素后面的索引位置。如果這個(gè)值大于提取區(qū)域的寬度,那么數(shù)組中,在一行最后和下一行開(kāi)始之間就會(huì)有一些未使用的索引。
存儲(chǔ)像素?cái)?shù)據(jù)
圖 2 顯示 BufferedImage.getRGB(...) 如何在提取了 AWT 圖像的矩形區(qū)域后填充整數(shù)緩沖區(qū)。圖中下面的部分表示整數(shù)緩沖區(qū)。每一個(gè)框表示緩沖區(qū)中包含一個(gè)像素 4 字節(jié) ARBG 值的一個(gè)值。括號(hào)中的數(shù)字表示像素在圖像中的坐標(biāo)。
圖 2. 從 AWT 圖像中提取像素值
在這種情況下,不使用任何 offset,這意味著第一個(gè)像素將保存在緩沖區(qū)索引 0 的位置。scansize 的值取要提取的區(qū)域的寬度,這意味著提取的一行中的第一個(gè)像素會(huì)接著前一行的最后一個(gè)像素的緩沖區(qū)位置。使用這些參數(shù),整數(shù)的緩沖區(qū)就一定會(huì)足夠大,可以包含 w*h 個(gè)整數(shù)。當(dāng)每一個(gè)像素的顏色信息都存儲(chǔ)到了一個(gè)整數(shù)的簡(jiǎn)單緩沖區(qū)后,就可以將這些信息傳輸?shù)?SWT 屏外圖像中。
創(chuàng)建 SWT 圖像
SWT Image 類(lèi)似于 AWT BufferedImage,因?yàn)樗南袼財(cái)?shù)據(jù)可以有直接讀或者寫(xiě)操作訪(fǎng)問(wèn)。這意味著可以通過(guò)直接讀取或者修改圖像的數(shù)據(jù),來(lái)設(shè)置或者取得圖像中任何像素或者任何一組像素的顏色值。不過(guò), SWT API 與相應(yīng)的 AWT API 有很大不同,并且更容易使用。
SWT 的 Image 類(lèi)提供了幾個(gè)構(gòu)造函數(shù),可以完成以下任務(wù):
通過(guò)將一個(gè)文件名或者一個(gè) InputStream 作為參數(shù)傳遞給構(gòu)造函數(shù)裝載一個(gè)現(xiàn)有的圖像。圖像的格式必須是所支持的格式之一:BMP、GIF、JPG、PNG、Windows ICO 等。
構(gòu)造一個(gè)指定大小的空?qǐng)D像??梢酝ㄟ^(guò)修改其像素值或者向它拷貝一個(gè) SWT 圖形上下文的內(nèi)容 (GC) 來(lái)繪制該圖像。構(gòu)造一個(gè)用像素值的現(xiàn)有緩沖區(qū)進(jìn)行初始化的圖像。您將使用第三個(gè)構(gòu)造函數(shù)創(chuàng)建一個(gè) SWT 圖像,它是所繪制的 AWT 圖像的副本。
關(guān)于 ImageData 類(lèi)
有關(guān)圖像的像素?cái)?shù)據(jù)的信息包含在它的 ImageData 中。ImageData 是一個(gè)包含有關(guān)圖像大小、調(diào)色板、顏色值和透明度信息的類(lèi)。應(yīng)當(dāng)特別關(guān)注以下 ImageData 字段:
width 和 height 指定圖像的大小。
depth 指定圖像的顏色深度??赡艿闹禐?1、2、4、8、16、24 或者 32,指定編碼每一個(gè)像素的值所使用的位數(shù)。
palette 包含一個(gè) PaletteData 對(duì)象,它存儲(chǔ)有關(guān)圖像的顏色模型的信息。與 AWT 一樣,SWT 的顏色模型可以是索引或者直接的。如果顏色模型是索引的,那么 PaletteData 包含顏色索引。如果它是直接的,那么它包含轉(zhuǎn)換(shift)信息,表明應(yīng)當(dāng)如何從像素的整數(shù)值中提取出顏色的 RGB 成分。
data 包含包含有像素值的字節(jié)緩沖區(qū)。與 AWT 緩沖區(qū)不同,SWT 緩沖區(qū)不是包含每一個(gè)像素的一種顏色值的整數(shù)數(shù)組。相反,它包含字節(jié)值。字節(jié)編碼的方法取決于所使用的顏色深度。對(duì)于一個(gè) 8 位的圖像,數(shù)組中的一個(gè)字節(jié)正好表示圖像中一個(gè)像素的值。對(duì)于 16 位圖像,每一個(gè)像素值編碼為緩沖區(qū)中的兩個(gè)字節(jié)。這兩個(gè)字節(jié)以最低有效字節(jié)順序存儲(chǔ)。對(duì)于 24 或者 32 位圖像,每一個(gè)像素值以最高有效位字節(jié)順序編碼為緩沖區(qū)中的三個(gè)或者四個(gè)字節(jié)。
bytesPerLine 表明緩沖區(qū)中有多少字節(jié)用于編寫(xiě)圖像中一行像素的所有像素值。transparentPixel 定義用于圖像中透明度的像素值。我們將使用帶有一個(gè)透明度顏色信道的 24 位圖像。圖像中的每一個(gè)像素都編碼為數(shù)組中的三個(gè)字節(jié),順序?yàn)榧t、綠和藍(lán)成分。
轉(zhuǎn)換圖像
知道了圖像數(shù)據(jù)就可以容易地將 AWT 圖像轉(zhuǎn)換為 SWT 圖像。只要將(由 AWT 圖像利用 getRGB(...) 返回的)整數(shù)緩沖區(qū)轉(zhuǎn)換為 SWT 圖像所使用的字節(jié)緩沖。圖 3 顯示了在 SWT 圖像的緩沖區(qū)中這些值是如何存儲(chǔ)的。
圖 3. 將像素值寫(xiě)入 SWT 圖像
和圖 2 中一樣,上圖中下面的部分顯示了圖像緩沖區(qū)的內(nèi)部表示。括號(hào)中的數(shù)字顯示在緩沖區(qū)中表示其顏色值的那個(gè)像素的坐標(biāo)。盡管每一個(gè)像素都用三個(gè)字節(jié)編碼,但是對(duì)于 24 位圖像,緩沖區(qū)中一行像素的大小并不總是 3*width。緩沖區(qū)中兩行像素之間可能有一些索引未使用。要知道圖像中每一行像素真正使用了多少字節(jié)(這樣就可知道緩沖區(qū)中下一行從哪個(gè)索引位置開(kāi)始),必須使用 ImageData 字段的 bytesPerLine 值。
SWT 到 Java 2D 渲染器
清單 1 顯示實(shí)現(xiàn)了屏外圖像技術(shù)的一般性渲染器(renderer)的源代碼。這個(gè)渲染器可以在 SWT 組件或者 Draw2D 圖像上繪制時(shí)透明地使用 Java 2D 例程。
清單 1. SWT/Draw2D Java 2D renderer
package swtgraphics2d;import java.awt.Graphics2D;import java.awt.image.BufferedImage;import org.eclipse.swt.graphics.GC;import org.eclipse.swt.graphics.Image;import org.eclipse.swt.graphics.ImageData;import org.eclipse.swt.graphics.PaletteData;import org.eclipse.swt.widgets.Display;/** * Helper class allowing the use of Java 2D on SWT or Draw2D graphical * context. * @author Yannick Saillet */public class Graphics2DRenderer { private static final PaletteData PALETTE_DATA = new PaletteData(0xFF0000, 0xFF00, 0xFF); private BufferedImage awtImage; private Image swtImage; private ImageData swtImageData; private int[] awtPixels; /** RGB value to use as transparent color */ private static final int TRANSPARENT_COLOR = 0x123456; /** * Prepare to render on a SWT graphics context. */ public void prepareRendering(GC gc) { org.eclipse.swt.graphics.Rectangle clip = gc.getClipping(); prepareRendering(clip.x, clip.y, clip.width, clip.height); } /** * Prepare to render on a Draw2D graphics context. */ public void prepareRendering(org.eclipse.draw2d.Graphics graphics) { org.eclipse.draw2d.geometry.Rectangle clip = graphics.getClip(new org.eclipse.draw2d.geometry.Rectangle()); prepareRendering(clip.x, clip.y, clip.width, clip.height); } /** * Prepare the AWT offscreen image for the rendering of the rectangular * region given as parameter. */ private void prepareRendering(int clipX, int clipY, int clipW, int clipH) { // check that the offscreen images are initialized and large enough checkOffScreenImages(clipW, clipH); // fill the region in the AWT image with the transparent color java.awt.Graphics awtGraphics = awtImage.getGraphics(); awtGraphics.setColor(new java.awt.Color(TRANSPARENT_COLOR)); awtGraphics.fillRect(clipX, clipY, clipW, clipH); } /** * Returns the Graphics2D context to use. */ public Graphics2D getGraphics2D() { if (awtImage == null) return null; return (Graphics2D) awtImage.getGraphics(); } /** * Complete the rendering by flushing the 2D renderer on a SWT graphical * context. */ public void render(GC gc) { if (awtImage == null) return; org.eclipse.swt.graphics.Rectangle clip = gc.getClipping(); transferPixels(clip.x, clip.y, clip.width, clip.height); gc.drawImage(swtImage, clip.x, clip.y, clip.width, clip.height, clip.x, clip.y, clip.width, clip.height); } /** * Complete the rendering by flushing the 2D renderer on a Draw2D * graphical context. */ public void render(org.eclipse.draw2d.Graphics graphics) { if (awtImage == null) return; org.eclipse.draw2d.geometry.Rectangle clip = graphics.getClip(new org.eclipse.draw2d.geometry.Rectangle()); transferPixels(clip.x, clip.y, clip.width, clip.height); graphics.drawImage(swtImage, clip.x, clip.y, clip.width, clip.height, clip.x, clip.y, clip.width, clip.height); } /** * Transfer a rectangular region from the AWT image to the SWT image. */ private void transferPixels(int clipX, int clipY, int clipW, int clipH) { int step = swtImageData.depth / 8; byte[] data = swtImageData.data; awtImage.getRGB(clipX, clipY, clipW, clipH, awtPixels, 0, clipW); for (int i = 0; i < clipH; i++) { int idx = (clipY + i) * swtImageData.bytesPerLine + clipX * step; for (int j = 0; j < clipW; j++) { int rgb = awtPixels[j + i * clipW]; for (int k = swtImageData.depth - 8; k >= 0; k -= 8) { data[idx++] = (byte) ((rgb >> k) & 0xFF); } } } if (swtImage != null) swtImage.dispose(); swtImage = new Image(Display.getDefault(), swtImageData); } /** * Dispose the resources attached to this 2D renderer. */ public void dispose() { if (awtImage != null) awtImage.flush(); if (swtImage != null) swtImage.dispose(); awtImage = null; swtImageData = null; awtPixels = null; } /** * Ensure that the offscreen images are initialized and are at least * as large as the size given as parameter. */ private void checkOffScreenImages(int width, int height) { int currentImageWidth = 0; int currentImageHeight = 0; if (swtImage != null) { currentImageWidth = swtImage.getImageData().width; currentImageHeight = swtImage.getImageData().height; } // if the offscreen images are too small, recreate them if (width > currentImageWidth || height > currentImageHeight) { dispose(); width = Math.max(width, currentImageWidth); height = Math.max(height, currentImageHeight); awtImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); swtImageData = new ImageData(width, height, 24, PALETTE_DATA); swtImageData.transparentPixel = TRANSPARENT_COLOR; awtPixels = new int[width * height]; } }}
這個(gè)渲染器包含在一個(gè)實(shí)用程序類(lèi)中。這個(gè)類(lèi)包含并管理屏外圖像技術(shù)所需要的 AWT 和 SWT 屏外圖像的引用。還要注意:
字段 swtImageData 和 awtPixels 分別是在像素轉(zhuǎn)移時(shí)包含 SWT 圖像的像素值的緩沖區(qū)和用于包含 AWT 圖像的像素值的緩沖區(qū)。
常量 TRANSPARENT_COLOR 包含一個(gè)作為 SWT 圖像中透明顏色的 RGB 值。因?yàn)楸仨毝x作為透明度信道的顏色以繪制背景,所以必須為此保留一個(gè)顏色值。在代碼中我使用了隨機(jī)值 0x123456。所有使用這個(gè)顏色值的像素都按透明處理。如果這個(gè)值所表示的顏色有可能在繪制操作中用到,可以用另一個(gè)值表示透明度。
渲染器是如何工作的
SWT/Draw2D Java 2D 渲染器的工作過(guò)程如下:
在可以使用渲染器之前,必須以至少與要繪制的區(qū)域一樣的尺寸初始化其屏外圖像和緩沖區(qū)。這是通過(guò)調(diào)用方法 prepareRendering(...) 完成的。根據(jù)是在 SWT 還是 Draw2D 圖形上下文中進(jìn)行渲染,這個(gè)方法以一個(gè) SWT GC 或者一個(gè) Draw2D Graphics 對(duì)象作為參數(shù)。
接下來(lái),prepareRendering 從圖形上下文中提取剪裁矩形(clip rectangle)—— 剪裁矩形,是可以修改其中像素的最大矩形區(qū)域。然后調(diào)用 private 方法 prepareRendering(int clipX, int clipY, int clipW, int clipH) 準(zhǔn)備要渲染的屏外圖像。這個(gè)方法獨(dú)立于所使用的圖形上下文的類(lèi)型,它可以是 SWT 或者 Draw2D。prepareRendering() 方法的工作過(guò)程如下:
它首先檢查 AWT 和 SWT 屏外圖像已實(shí)例化并足以包含要繪制的區(qū)域,這是由方法 checkOffScreenImages(clipW, clipH) 完成的。如果屏外圖像已經(jīng)實(shí)例化,但是不夠大,就會(huì)放棄它并以所需要大小重新創(chuàng)建一個(gè)。如果屏外圖像大于要繪制的區(qū)域,那么就會(huì)重復(fù)使用它,并只修改相應(yīng)于要繪制的區(qū)域的那一部分。
完成這種檢查后,用為透明度信道保留的顏色 TRANSPARENT_COLOR 填充繪制區(qū)域。這個(gè)圖像就可以用來(lái)進(jìn)行 Java 2D 操作了。當(dāng)渲染器準(zhǔn)備好進(jìn)行剪裁區(qū)域中的 Java 2D 繪制操作后,可以從 BufferedImage AWT 獲得 Java 2D Graphics2D 上下文。這個(gè)圖形上下文將用于所有 Java 2D 繪制例程。每一次繪制操作修改 AWT 屏外圖像。
當(dāng)所有 Java 2D 繪制操作都完成后,繪制區(qū)域的像素必須從 AWT 轉(zhuǎn)換為 SWT 屏外圖像,然后繪制到 SWT 或者 Draw2D 圖形上下文中。這個(gè)操作是由方法 render(GC) 或者 render(Graphics) 完成的。這兩個(gè)方法都在內(nèi)部調(diào)用 private 方法 transferPixels(...),該方法將像素從 AWT 轉(zhuǎn)換為 SWT。
如果不再需要渲染器了或者必須釋放資源,可以調(diào)用 dispose() 方法,以清除渲染器所使用的屏外圖像和緩沖區(qū)。這會(huì)釋放資源,但是在再次需要渲染器時(shí)要花時(shí)間重新創(chuàng)建緩沖區(qū)。需要根據(jù)組件重新繪制的頻度以及重新繪制區(qū)域應(yīng)有多大來(lái)判斷應(yīng)當(dāng)什么時(shí)候調(diào)用 dispose()。
清單 2 顯示如何用渲染器在 SWT Canvas 上繪制一些旋轉(zhuǎn)文字。
清單 2. 在 SWT Canvas 上的使用示例
Canvas canvas = new Canvas(shell, SWT.NO_BACKGROUND);final Graphics2DRenderer renderer = new Graphics2DRenderer();canvas.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { Point controlSize = ((Control) e.getSource()).getSize(); GC gc = e.gc; // gets the SWT graphics context from the event renderer.prepareRendering(gc); // prepares the Graphics2D renderer // gets the Graphics2D context and switch on the antialiasing Graphics2D g2d = renderer.getGraphics2D(); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); // paints the background with a color gradient g2d.setPaint(new GradientPaint(0.0f, 0.0f, java.awt.Color.yellow, (float) controlSize.x, (float) controlSize.y, java.awt.Color.white)); g2d.fillRect(0, 0, controlSize.x, controlSize.y); // draws rotated text g2d.setFont(new java.awt.Font("SansSerif", java.awt.Font.BOLD, 16)); g2d.setColor(java.awt.Color.blue); g2d.translate(controlSize.x / 2, controlSize.y / 2); int nbOfSlices = 18; for (int i = 0; i < nbOfSlices; i++) { g2d.drawString("Angle = " + (i *360/ nbOfSlices) + "\u00B0", 30, 0); g2d.rotate(-2 * Math.PI / nbOfSlices); } // now that we are done with Java 2D, renders Graphics2D operation // on the SWT graphics context renderer.render(gc); // now we can continue with pure SWT paint operations gc.drawOval(0, 0, controlSize.x, controlSize.y); }});
代碼說(shuō)明:
創(chuàng)建一次渲染器,并在每次需要重新繪制畫(huà)布(canvas) 時(shí)重復(fù)使用它。
實(shí)例化畫(huà)布,并在它上面添加一個(gè) PaintListener 以執(zhí)行繪制操作。
從 PaintEvent 獲得圖形上下文 gc。
渲染器是在圖形上下文中準(zhǔn)備的,這意味著屏外圖像可以接受繪制操作。
在下一步,獲得 Java 2D 圖形上下文 g2d。
然后實(shí)現(xiàn)一組 Java 2D 繪制操作以用漸變顏色繪制背景并且每 20 度繪制一個(gè)旋轉(zhuǎn)的文字。這個(gè)過(guò)程使用圖形上下文的平移和幾個(gè)旋轉(zhuǎn)。
最后,調(diào)用 render(GC) 方法以將 Java 2D 繪制操作傳輸?shù)?SWT 圖形上下文??梢栽谕焕L制例程中使用 Java 2D 和純 SWT 繪制操作。
渲染操作的結(jié)果
渲染操作的結(jié)果如圖 4 所示。在這個(gè)例子中,沒(méi)有丟棄渲染器,在每次繪制 Canvas 時(shí)可以重復(fù)使用它的屏外圖像和內(nèi)部緩沖區(qū),這樣可以節(jié)省實(shí)例化和垃圾收集的時(shí)間。記住,如果畫(huà)布不需要經(jīng)常重新繪制,并且渲染器所占用的資源非常重要,那么可以在每次繪制操作后丟棄渲染器。
圖 4. 使用示例:用 Java 2D 例程幫助繪制 SWT
清單 3 顯示如何在 Draw2D 圖像中實(shí)現(xiàn)同樣的例子。在這里是通過(guò)覆蓋 Figure 的方法 paintClientArea(Graphics) 來(lái)實(shí)現(xiàn)繪制 Figure。Graphics2DRenderer 的使用與上一個(gè)例子完全一樣。惟一的區(qū)別是,方法 prepareRendering 和 render 這一次是以一個(gè) Draw2D Graphics 而不是一個(gè) SWT GC 為參數(shù)調(diào)用的。
清單 3. 對(duì) Draw2D 圖的使用示例
final Graphics2DRenderer renderer = new Graphics2DRenderer();IFigure figure = new Figure() { protected void paintClientArea(org.eclipse.draw2d.Graphics graphics) { Dimension controlSize = getSize(); renderer.prepareRendering(graphics); // prepares the Graphics2D renderer // gets the Graphics2D context Graphics2D g2d = renderer.getGraphics2D(); (...) // does the Java 2D painting // now that we are done with Java 2D, renders Graphics2D operation // on the Draw2D graphics context renderer.render(graphics); // now we can continue with pure Draw2D paint operations graphics.drawOval(0, 0, controlSize.width, controlSize.width); }};
本文展示的技術(shù),使得將 Java 2D 功能集成到 SWT 和 GEF 應(yīng)用程序中成為可能并且相當(dāng)容易。我一步一步地展示了如何結(jié)合 SWT 和 Java 2D 的最好功能并將結(jié)果繪制到任何 SWT 組件或者 GEF Draw2D 圖像上。這里展示的技術(shù)就像代碼示例這樣簡(jiǎn)單:只用幾行代碼就可以實(shí)現(xiàn),不需要依賴(lài)任何外部庫(kù),也不需要啟動(dòng)一個(gè) AWT 線(xiàn)程。