除了内置的形状类的能力之外,Java 2D 还具有通过组合 Shape
对象来构造复杂形状的能力。这一功能称为构造型区域几何 或 CAG。
Shape
接口的特殊实现,即 java.awt.geom.Area
类,在 Shape
类上执行各种二进制操作。
有四种组合操作:
add()
:组合两个形状。它不必有任何共同区域。subtract()
:除去指定区域。intersect()
:创建一个只含有源区域和目标区域共有空间的新的形状。exclusiveOr()
:从两个形状中除去重叠区域。 上述操作创建一个新 Area
,它是源对象和指定的目标对象组合的结果。源对象可以在 Area
对象的构造器中指定。因为 Area
本身就是 Shape
,所以可以组合任意复杂的形状。
请记住,渲染引擎必须处理的段的数目随形状的复杂而上升,从而会降低性能。
缓冲的图像 | 第 2 页(共5 页) |
类似于 Graphics2D
继承 Graphics
的方式,java.awt.image.BufferedImage
通过在 Java 2D API 中提供一组强大的功能替换了 AWT Image
类。BufferedImage
是 Image
的继承,因此它同 Java 2D 以前的代码完全兼容。JDK 1.2 的实现规定:图形系统返回的任何 Image
对象都是 BufferedImage
,这与所有的图形环境实际上都是 Graphics2D
实例很相象。
Image
只可通过获取图形环境然后在其上着色来进行修改,而 BufferedImage
与 Image
不同,它提供对底层图像数据的直接访问。BufferedImage
还提供通过称为过滤的过程来进行修改,这一过程在图像上提供了类似于高级图像编辑软件中找到的那些操作。
对 BufferedImage
进行了特殊设计以支持 Java 高级图像 API,它支持多色模式和光栅格式。
从图像创建缓冲图像 | 第 3 页(共5 页) |
变换是一组操作,它们基于对图形元素执行各种修改的矩阵算术。要使用这些能力并不一定要理解线性代数;java.awt.geom.AffineTransform
类封装了所有可用操作。可以将变换应用于 Shape
、Font
和 BufferedImage
,也可以将它直接应用于图形环境。
变换可以提供几种操作,这些操作可以结合起来使用。
正如您能想象到的那样,变换是不可交换的。也就是,如果以不同顺序应用一组变换操作,那么它们可能会产生不同的结果。很显然,如果您按照某人的吩咐“转九十度再向北走三步”与您“向北走三步再转九十度”所到达的地方将不一样。
下面的代码获取一个 Image
类型的对象并将其转换为一个 BufferedImage
实例:
public static BufferedImage createBufferedImage(Image image)
{
// Create the buffered image.
BufferedImage bufferedImage =
new BufferedImage(image.getWidth(null),
image.getHeight(null), BufferedImage.TYPE_INT_RGB);
// The fill operation will expand the buffer to accommodate
// the copy of the image
Graphics g = bufferedImage.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, image.getWidth(null),
image.getHeight(null));
// Copy image to buffered image and return
g.drawImage(image, 0, 0, null);
g.dispose();
return bufferedImage;
}
构造型区域几何 | 第 1 页(共5 页) |
示例:模拟时钟 | 第 5 页(共5 页) |
下面是一个使用 GeneralPath
和 AffineTransform
类来创建实时模拟时钟的示例类。
package com.zelator.clock2d;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.Calendar;
import javax.swing.*;
public class Clock2DPanel extends JPanel implements ActionListener
{
// Create a shape for the face of the clock
protected static Ellipse2D face = new Ellipse2D.Float(3, 3, 94, 94);
// Create a path that represents a tick mark
protected static GeneralPath tick = new GeneralPath();
static
{
tick.moveTo(100, 100);
tick.moveTo(49, 0);
tick.lineTo(51, 0);
tick.lineTo(51, 6);
tick.lineTo(49, 6);
tick.lineTo(49, 0);
}
// Create a cool hour hand
protected static GeneralPath hourHand = new GeneralPath();
static
{
hourHand.moveTo(50, 15);
hourHand.lineTo(53, 50);
hourHand.lineTo(50, 53);
hourHand.lineTo(47, 50);
hourHand.lineTo(50, 15);
}
// Create a cool minute hand
protected static GeneralPath minuteHand = new GeneralPath();
static
{
minuteHand.moveTo(50, 2);
minuteHand.lineTo(53, 50);
minuteHand.lineTo(50, 58);
minuteHand.lineTo(47, 50);
minuteHand.lineTo(50, 2);
}
// And a cool second hand
protected static GeneralPath secondHand = new GeneralPath();
static
{
secondHand.moveTo(49, 5);
secondHand.lineTo(51, 5);
secondHand.lineTo(51, 62);
secondHand.lineTo(49, 62);
secondHand.lineTo(49, 5);
}
// Create some colors for the pieces of the clock
protected static Color faceColor = new Color(220, 220, 220);
protected static Color hourColor = Color.red.darker();
protected static Color minuteColor = Color.blue.darker();
protected static Color secondColor = new Color(180, 180, 0);
protected static Color pinColor = Color.gray.brighter();
// Create circles for the pivot and center pin
protected Ellipse2D pivot = new Ellipse2D.Float(47, 47, 6, 6);
protected Ellipse2D centerPin = new Ellipse2D.Float(49, 49, 2, 2);
// Create three transforms that center around the pivot point
protected AffineTransform hourTransform =
AffineTransform.getRotateInstance(0, 50, 50);
protected AffineTransform minuteTransform =
AffineTransform.getRotateInstance(0, 50, 50);
protected AffineTransform secondTransform =
AffineTransform.getRotateInstance(0, 50, 50);
// Create a timer that fires once a second and a Calendar
// instance for getting the time values
protected Timer timer = new Timer(1000, this);
protected Calendar calendar = Calendar.getInstance();
// Constructor - hardcode a preferred size of 100x100
public Clock2DPanel()
{
setPreferredSize(new Dimension(100, 100));
}
// Invoked when panel is added to a container
public void addNotify()
{
// Call the superclass and start the timer
super.addNotify();
timer.start();
}
// Invoked when panel is removed from a container
public void removeNotify()
{
// Call the superclass and stop the timer
timer.stop();
super.removeNotify();
}
//
public void actionPerformed(ActionEvent event)
{
// Update the calendar's time
this.calendar.setTime(new java.util.Date());
// Extract the hours minutes and seconds
int hours = this.calendar.get(Calendar.HOUR);
int minutes = this.calendar.get(Calendar.MINUTE);
int seconds = this.calendar.get(Calendar.SECOND);
// Using a little trigonometry, set the transforms to rotate
// each hand into the proper position. Center the rotation
// around the pivot point (50, 50) instead of the origin
hourTransform.setToRotation(((double) hours) *
(Math.PI / 6.0), 50, 50);
minuteTransform.setToRotation(((double) minutes) *
(Math.PI / 30.0), 50, 50);
secondTransform.setToRotation(((double) seconds) *
(Math.PI / 30.0), 50, 50);
// Force the component to repaint ASAP
repaint();
}
// This is an alternative to creating a UI delegate. Since JPanel's
// paint() method only paints the border and backgound, we can just
// override the paint method of the component to do the graphics.
public void paint(Graphics g)
{
// Call the superclass first to paint the border (if one is assigned)
super.paint(g);
// Get the graphics context and turn on anti-aliasing
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Set the paint for the clock face and fill it in
g2.setPaint(faceColor);
g2.fill(face);
// Set the paint to black and draw the clock's outline
g2.setPaint(Color.black);
g2.draw(face);
// Fill in the 12 ticks around the face of the clock
for (double p = 0.0; p < 12.0; p += 1.0)
{
// This is probably terribly inefficient and should be
// done statically or in the constructor - draw the
// tick as a transformed shape that is rotated.
g2.fill(tick.createTransformedShape(
AffineTransform.getRotateInstance((Math.PI / 6.0) * p,
50, 50)));
}
// Set the paint and draw the hour hand. It is lowest in the
// 'z-order' so will appear underneath the other hands. Notice
// how each hand is transformed by a different <AffineTransform>.
g2.setPaint(hourColor);
g2.fill(hourHand.createTransformedShape(hourTransform));
// Set the paint and draw the minute hand, the second hand,
// the pivot and the center pin
g2.setPaint(minuteColor);
g2.fill(minuteHand.createTransformedShape(minuteTransform));
g2.setPaint(secondColor);
g2.fill(secondHand.createTransformedShape(secondTransform));
g2.fill(pivot);
g2.setPaint(pinColor);
g2.fill(centerPin);
}
// A little test frame to show off our fancy clock
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setLocation(700, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new Clock2DPanel());
frame.pack();
frame.show();
}
}