如何在Java中繪製一個漂亮的圓
1. 概述
在本教程中,我們將建立一個繪製高品質圓的小型 Swing 元件,並根據各種要求進行改進,例如居中、描邊和小尺寸的精度。
我們將採用官方的 Java2D API 和繪製指南。
2. 項目結構
我們先寫一個繼承自JPanel類別來存取paintComponent()方法。這可以與框架的雙緩衝機制集成,確保在繪製之前清除背景:
public final class CirclePanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
try {
} finally {
g2.dispose();
}
}
}
根據建議模式,我們建立了一個Graphics2D變數來處理傳入的Graphics ,然後將其釋放以防止記憶體洩漏。
3. 畫圓
雖然我們可以使用drawOval(x, y, w, h)繪製一個圓,但 Java2D 的形狀 API 能提供更可預測、更可組合的結果。因此,讓我們使用等寬等高的Ellipse2D來表示一個圓,並將其傳遞給draw()和fill()方法:
double diameter = 160;
double x = 40;
double y = 40;
Shape circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2.setPaint(new Color(0xB3E5FC));
g2.fill(circle);
g2.setPaint(new Color(0x01579B));
g2.setStroke(new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.draw(circle);
為了在圓形上添加一些顏色,我們可以使用setPaint()並傳遞一個顏色,也可以使用setStroke()來更好地看到輪廓。
現在運行我們的程式碼會得到一個不錯的圓,但這遠非我們想要的結果:
讓我們透過添加一些改進來增強我們的圓。具體來說,我們將根據視窗尺寸動態調整其大小並使其居中:
float stroke = 2f;
int pad = 12;
double diameter = Math.min(getWidth(), getHeight()) - pad - stroke;
double cx = getWidth() / 2.0;
double cy = getHeight() / 2.0;
double x = cx - diameter / 2.0;
double y = cy - diameter / 2.0;
Shape circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2.setStroke(new BasicStroke(stroke, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
程式碼透過取較小的視窗尺寸並減去內邊距來動態調整圓的大小;然後,它透過計算圓心並將其定位到中心位置來使其居中。
因此,筆畫會延伸到形狀邊界的一半內部和一半外部。考慮到這一點,我們可以防止輪廓在緊湊的佈局中被裁剪:
雖然現在看起來好了一些,但距離畫出清晰的圓還差得很遠。
4. 高品質渲染技巧
Graphics2D允許我們透過使用setRenderingHint()來取得某些提示。
首先,我們啟用抗鋸齒功能,使邊緣平滑,並消除對角線和曲線上的階梯效應:
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
單靠抗鋸齒就已經能顯著提升畫質。但是,當我們繪製非常小的圓形或需要亞像素級精度時,我們也會啟用純筆畫控制:
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
此提示告訴 Java2D以與設備無關的方式計算筆畫幾何形狀,這有助於使細小的圓在不同的位置和大小上看起來更加均勻。
我們可以透過添加渲染提示來進一步提高品質:
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
在添加提示後,即使在較小的視窗尺寸下,我們也能看到圓圈的明顯改善:
在迭代過程中,我們可以嘗試不同的直徑、筆畫粗細和位置。由於我們使用的是形狀,因此也可以套用變換:
g2.translate(10, 10);
g2.scale(1.25, 1.25);
這樣,我們就可以在不同的情況下測試我們的圓。
5. 小圓圈和圖標
在較小的圖示尺寸(12-24像素)下,細微的圓角差異會變得更加明顯。我們經常會注意到輪廓的一側看起來略粗。即使開啟了抗鋸齒功能,在這個尺寸下,像素也無法呈現完美的幾何形狀。
為了解決這個問題,我們可以將圓形渲染到螢幕外的BufferedImage中,應用高品質的提示,然後在需要的地方繪製該圖像。這樣可以提供一致的結果,並且在重複使用同一個圓形時節省 CPU 時間。
根據我們想要盡可能高的流暢度還是最大的運作效率,通常有兩種方法可以實現這一點。
5.1. 超採樣離屏渲染
在這種方法中,我們先以較大的解析度繪製圓,通常是最終尺寸的 2 倍或 3 倍,然後在上色時將其縮小:
BufferedImage makeSupersampledCircle(int finalSize, int scale, float stroke) {
int hi = finalSize * scale;
BufferedImage img = new BufferedImage(hi, hi, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
try {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
double d = hi - stroke;
Shape circle = new Ellipse2D.Double(stroke / 2.0, stroke / 2.0, d, d);
g2.setPaint(new Color(0xBBDEFB));
g2.fill(circle);
g2.setPaint(new Color(0x0D47A1));
g2.setStroke(new BasicStroke(stroke, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.draw(circle);
} finally {
g2.dispose();
}
return img;
}
這種稱為超採樣的技術,在影像重採樣過程中有效地執行了額外的抗鋸齒處理,從而產生平滑的邊緣。
要使用它,我們在paintComponent()中繪製縮小後的大圖像:
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.drawImage(hiResImage, x, y, finalSize, finalSize, null);
這個額外的重採樣過程可以隱藏像素鋸齒,即使是 Java2D 的抗鋸齒功能也無法消除小圓上的鋸齒:
缺點是記憶體和 CPU 使用率略高,但這是製作清晰的徽章圖示、狀態指示燈或工具列圓點的技術。
5.2. 硬體相容的BufferedImage
為了提高速度和簡化操作,我們使用硬體相容的透明影像,直接以最終尺寸渲染圓形:
BufferedImage makeCircleBadge(int size, float stroke) {
GraphicsConfiguration gc = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
BufferedImage img = gc.createCompatibleImage(size, size, Transparency.TRANSLUCENT);
Graphics2D g2 = img.createGraphics();
try {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
double d = size - stroke;
Shape circle = new Ellipse2D.Double(stroke / 2.0, stroke / 2.0, d, d);
g2.setPaint(new Color(0xBBDEFB));
g2.fill(circle);
g2.setPaint(new Color(0x0D47A1));
g2.setStroke(new BasicStroke(stroke, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.draw(circle);
} finally {
g2.dispose();
}
return img;
}
這種方法使用createCompatibleImage() ,因此產生的點陣圖與顯示器的顏色模型相匹配,可以非常有效率地繪製。
這種方法可以產生背景透明的簡潔圖示。由於影像格式針對 GPU 進行了最佳化,因此在表格、按鈕或儀表板中重複繪製該圖示的速度非常快:
如果我們快取圖像並重複使用,即使有數百個小圓形指示器,使用者介面也能保持回應。
我們甚至可以將這兩種方法結合起來,在啟動時渲染一次超採樣影像,並在整個使用者介面中重複使用縮小的點陣圖,從而獲得兩全其美的效果。
6. 結論
在本文中,我們探討了在 Java 中繪製美觀圓形的幾種方法。
我們看到,正確使用Graphics2D 、抗鋸齒和渲染提示可以產生平滑對稱的效果。我們也學習如何使用BufferedImage進行離屏渲染(無論是超採樣還是硬體相容),從而創建清晰的小圖示。借助這些技巧,可以輕鬆渲染出在所有 Java 應用程式中看起來乾淨一致的圓形。
和往常一樣,程式碼可以在 GitHub 上找到。