Java2D開發技巧之“燈光與陰影”
一、 引言
在本文中,我們將向你展示如何為扁平形狀添加一種燈光效果以實現一種類3D外觀。
也許你比較滿意于自己的文字表達能力,但一幅圖片往往能夠產生更好的效果。對于圖形處理來說,也是如此;不妨請參考一下圖1中的兩種圖形。在本文中,我將向你展示如何克服左邊扁平形狀所帶來的煩惱而以一種更為光滑的更具舒服感的形狀代替。
二、 實現技術分析
借助于適當的顏色,你可以使用本文中介紹的技術來模擬一種彩色光閃耀"越過"你的形狀,從而生成一種微妙的發光效果。我們是如何實現這一效果的呢?請分析下面的代碼;在方法drawBorderGlow()上面的注釋更為細致地介紹了關鍵實現方法:
注意,在上面的例子中,我把一些可選的代碼行加上了注釋。你可以去掉這些注釋并觀察它們對生成效果的影響。
注意:聰明的讀者可以已經注意到,上面應用于paintBorderGlow()方法中的技術也可以用于沿形狀添加一種投影效果。你不妨先猜測一下如何實現這一點……好,時間到!不是在形狀的頂部生成邊緣(記住,修剪能夠確保筆劃僅影響形狀的內部),我們可以預先繞著我們的形狀生成一種可變的灰色邊界。這意味著,陰影筆劃將出現在我們的形狀的外邊;陰影筆劃的內部將會通過我們的形狀而有效地生成。
你可以把下面的一些代碼插入到上面的例子中以便在相應的同一個形狀上添加一種陰影邊界效果:
下面圖2是最終的結果圖像:
三、 小結
在本文中,我僅向你介紹了快速地添加一種陰影效果的方法。如果有時間的話,我很可能會使用一種亮灰色和一種非線性斜面來實現一種更為真實的效果。還要注意的是,這里介紹的僅是使用Java 2D實現投影效果的許多方法之一。注意,Romain在他的博客中已經討論了多種不同的投影實現方法。SwingLabs成員在SwingX工程中也提供了一種DropShadowBorder實現;而DropShadowPanel當前正在開發中。
在本文中,我們將向你展示如何為扁平形狀添加一種燈光效果以實現一種類3D外觀。
也許你比較滿意于自己的文字表達能力,但一幅圖片往往能夠產生更好的效果。對于圖形處理來說,也是如此;不妨請參考一下圖1中的兩種圖形。在本文中,我將向你展示如何克服左邊扁平形狀所帶來的煩惱而以一種更為光滑的更具舒服感的形狀代替。
![]() 圖1.普通扁平形狀與施加Java 2D效果后的形狀 |
二、 實現技術分析
借助于適當的顏色,你可以使用本文中介紹的技術來模擬一種彩色光閃耀"越過"你的形狀,從而生成一種微妙的發光效果。我們是如何實現這一效果的呢?請分析下面的代碼;在方法drawBorderGlow()上面的注釋更為細致地介紹了關鍵實現方法:
| import java.awt.geom.*; import java.awt.image.*; private static final Color clrHi = new Color(255, 229, 63); private static final Color clrLo = new Color(255, 105, 0); private static final Color clrGlowInnerHi = new Color(253, 239, 175, 148); private static final Color clrGlowInnerLo = new Color(255, 209, 0); private static final Color clrGlowOuterHi = new Color(253, 239, 175, 124); private static final Color clrGlowOuterLo = new Color(255, 179, 0); private Shape createClipShape() { float border = 20.0f; float x1 = border; float y1 = border; float x2 = width - border; float y2 = height - border; float adj = 3.0f; //幫助圓化類銳的拐角 float arc = 8.0f; float dcx = 0.18f * width; float cx1 = x1-dcx; float cy1 = 0.40f * height; float cx2 = x1+dcx; float cy2 = 0.50f * height; GeneralPath gp = new GeneralPath(); gp.moveTo(x1-adj, y1+adj); gp.quadTo(x1, y1, x1+adj, y1); gp.lineTo(x2-arc, y1); gp.quadTo(x2, y1, x2, y1+arc); gp.lineTo(x2, y2-arc); gp.quadTo(x2, y2, x2-arc, y2); gp.lineTo(x1+adj, y2); gp.quadTo(x1, y2, x1, y2-adj); gp.curveTo(cx2, cy2, cx1, cy1, x1-adj, y1+adj); gp.closePath(); return gp; } private BufferedImage createClipImage(Shape s) { // 創建一半透明的中間圖像,我們可以使用它來實現軟修剪效果 GraphicsConfiguration gc = g.getDeviceConfiguration(); BufferedImage img = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT); Graphics2D g2 = img.createGraphics(); //清除圖像,這樣所有的像素都具有零alpha g2.setComposite(AlphaComposite.Clear); g2.fillRect(0, 0, width, height); // 把我們的修剪形狀生成到圖像上。注意,我們啟動了 // 反走樣功能以實現軟修剪效果。你可以 //嘗試注釋掉啟動反走樣的這一行,那么 //你會看到通常的生硬的修剪效果. g2.setComposite(AlphaComposite.Src); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(Color.WHITE); g2.fill(s); g2.dispose(); return img; } private static Color getMixedColor(Color c1, float pct1, Color c2, float pct2) { float[] clr1 = c1.getComponents(null); float[] clr2 = c2.getComponents(null); for (int i = 0; i < clr1.length; i++) { clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2); } return new Color(clr1[0], clr1[1], clr1[2], clr1[3]); } //下面是實現技巧:為了實現發光效果,我們開始使用一種"內部"顏色粗筆 //和筆劃需要的形狀。然后,我們不斷地把筆變細, //并且不斷地移向"外部"顏色, //并且不斷地提高顏色的不透明度以便使其朝向形狀的內部看上去暗淡。 //我們使用已經生成到我們的目的圖像上的"修剪形狀",這樣以來, //SRC_ATOP規則就會修剪在我們的形狀外部的筆劃部分。 private void paintBorderGlow(Graphics2D g2, int glowWidth) { int gw = glowWidth*2; for (int i=gw; i >= 2; i-=2) { float pct = (float)(gw - i) / (gw - 1); Color mixHi = getMixedColor(clrGlowInnerHi, pct,clrGlowOuterHi, 1.0f - pct); Color mixLo = getMixedColor(clrGlowInnerLo, pct,clrGlowOuterLo, 1.0f - pct); g2.setPaint(new GradientPaint(0.0f, height*0.25f, mixHi,0.0f, height, mixLo)); //g2.setColor(Color.WHITE); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, pct)); g2.setStroke(new BasicStroke(i)); g2.draw(clipShape); } } Shape clipShape = createClipShape(); //Shape clipShape = new Ellipse2D.Float(width/4, height/4, width/2, height/2); //把背景清除為白色 g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); //設置修剪形狀 BufferedImage clipImage = createClipImage(clipShape); Graphics2D g2 = clipImage.createGraphics(); //使用漸變填充形狀 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setComposite(AlphaComposite.SrcAtop); g2.setPaint(new GradientPaint(0, 0, clrHi, 0, height, clrLo)); g2.fill(clipShape); //應用邊界發光效果 paintBorderGlow(g2, 8); g2.dispose(); g.drawImage(clipImage, 0, 0, null); |
注意,在上面的例子中,我把一些可選的代碼行加上了注釋。你可以去掉這些注釋并觀察它們對生成效果的影響。
注意:聰明的讀者可以已經注意到,上面應用于paintBorderGlow()方法中的技術也可以用于沿形狀添加一種投影效果。你不妨先猜測一下如何實現這一點……好,時間到!不是在形狀的頂部生成邊緣(記住,修剪能夠確保筆劃僅影響形狀的內部),我們可以預先繞著我們的形狀生成一種可變的灰色邊界。這意味著,陰影筆劃將出現在我們的形狀的外邊;陰影筆劃的內部將會通過我們的形狀而有效地生成。
你可以把下面的一些代碼插入到上面的例子中以便在相應的同一個形狀上添加一種陰影邊界效果:
| private void paintBorderShadow(Graphics2D g2, int shadowWidth) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); int sw = shadowWidth*2; for (int i=sw; i >= 2; i-=2) { float pct = (float)(sw - i) / (sw - 1); g2.setColor(getMixedColor(Color.LIGHT_GRAY, pct,Color.WHITE, 1.0f-pct)); g2.setStroke(new BasicStroke(i)); g2.draw(clipShape); } } //在我們繪制形狀的其它部分前應用邊界的陰影效果。 paintBorderShadow(g, 6); |
下面圖2是最終的結果圖像:
![]() 圖2.施加Java 2D效果后的最終結果形狀 |
三、 小結
在本文中,我僅向你介紹了快速地添加一種陰影效果的方法。如果有時間的話,我很可能會使用一種亮灰色和一種非線性斜面來實現一種更為真實的效果。還要注意的是,這里介紹的僅是使用Java 2D實現投影效果的許多方法之一。注意,Romain在他的博客中已經討論了多種不同的投影實現方法。SwingLabs成員在SwingX工程中也提供了一種DropShadowBorder實現;而DropShadowPanel當前正在開發中。

