一些具体对象被标记为Serializable,可以使用对象序列化存储和恢复,但像Area这样的其他对象不会实现接口并抛出错误.
但是由于我们经常被警告,这种天真的序列化在Java实现或版本中不一定稳定,所以我更喜欢使用某种形式的序列化.
这导致我们使用XMLEncoder和XMLDecoder从XML存储/恢复,但是它能够处理更少的Java 2D Shape对象.
两者的一些结果可以在下面看到.我们从6个形状开始,并尝试通过对象序列化和标准XML序列化来存储/恢复它们.
我们将如何通过XML正确存储所有Shape对象?
import java.awt.*; import java.awt.geom.*; import java.awt.image.BufferedImage; import java.beans.*; import java.io.*; import java.util.ArrayList; import javax.swing.*; import javax.swing.border.TitledBorder; public class Serialize2D { private JPanel ui; Serialize2D() { initUI(); } public void initUI() { if (ui != null) { return; } ui = new JPanel(new GridLayout(0,1)); int[] xpoints = {205,295,205,295}; int[] ypoints = {5,25,45}; Polygon polygon = new Polygon(xpoints,ypoints,xpoints.length); ArrayList<Shape> shapes = new ArrayList<Shape>(); int w = 45; shapes.add(new Rectangle2D.Double(5,5,90,40)); shapes.add(new Ellipse2D.Double(105,40)); shapes.add(polygon); shapes.add(new GeneralPath(new Rectangle2D.Double(5,55,40))); shapes.add(new Path2D.Double(new Rectangle2D.Double(105,40))); shapes.add(new Area(new Rectangle2D.Double(205,40))); addTitledLabelToPanel(shapes,"Original Shapes"); addTitledLabelToPanel( serializeToFromObject(shapes),"Serialize via Object"); addTitledLabelToPanel( serializeToFromXML(shapes),"Serialize via XML"); } public JComponent getUI() { return ui; } public ArrayList<Shape> serializeToFromObject(ArrayList<Shape> shapes) { ArrayList<Shape> shps = new ArrayList<Shape>(); try { ObjectOutputStream oos = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); for (Shape shape : shapes) { try { oos.writeObject(shape); } catch (Exception ex) { System.err.println(ex.toString()); } } oos.flush(); oos.close(); System.out.println("length Obj: " + baos.toByteArray().length); ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Object o = null; try { o = ois.readObject(); } catch (NotSerializableException ex) { System.err.println(ex.getMessage()); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } while (o != null) { shps.add((Shape) o); try { o = ois.readObject(); } catch (NotSerializableException ex) { System.err.println(ex.getMessage()); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } } return shps; } catch (IOException ex) { ex.printStackTrace(); } return shps; } public ArrayList<Shape> serializeToFromXML(ArrayList<Shape> shapes) { ArrayList<Shape> shps = new ArrayList<Shape>(); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLEncoder xmle = new XMLEncoder(baos); for (Shape shape : shapes) { xmle.writeObject(shape); } xmle.flush(); xmle.close(); System.out.println("length XML: " + baos.toByteArray().length); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); XMLDecoder xmld = new XMLDecoder(bais); Shape shape = (Shape) xmld.readObject(); while (shape != null) { shps.add(shape); try { shape = (Shape) xmld.readObject(); } catch (ArrayIndexOutOfBoundsException aioobe) { // we've read last object shape = null; } } xmld.close(); } catch (Exception ex) { ex.printStackTrace(); } return shps; } private final static String getType(Object o) { String s = o.getClass().getName(); String[] parts = s.split("\\."); s = parts[parts.length - 1].split("\\$")[0]; return s; } public static void drawShapesToImage( ArrayList<Shape> shapes,BufferedImage image) { Graphics2D g = image.createGraphics(); g.setRenderingHint( RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(Color.WHITE); g.fillRect(0,image.getWidth(),image.getHeight()); for (Shape shape : shapes) { String s = getType(shape); g.setColor(Color.GREEN); g.fill(shape); g.setColor(Color.BLACK); g.draw(shape); Rectangle r = shape.getBounds(); int x = r.x + 5; int y = r.y + 16; if (r.width * r.height != 0) { g.drawString(s,x,y); } } g.dispose(); } private void addTitledLabelToPanel(ArrayList<Shape> shapes,String title) { int w = 300; int h = 100; BufferedImage bi = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB); drawShapesToImage(shapes,bi); JLabel l = new JLabel(new ImageIcon(bi)); l.setBorder(new TitledBorder(title)); ui.add(l); } public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { Serialize2D ss = new Serialize2D(); JOptionPane.showMessageDialog(null,ss.getUI()); } }; SwingUtilities.invokeLater(r); } }
解决方法@H_301_16@
不幸的是,使用XMLEncoder / Decoder的Shape to XML的天真编码/解码通常会破坏Shape的所有重要信息!
所以要做到这一点,仍然使用上面提到的类,我们序列化和恢复正确构造的bean,它们表示从PathIterator获得的形状部分.这些豆是:
> PathBean,它存储形成Java-2D Shape形状的PathSegment对象的集合.
> PathSegment,其存储路径的特定部分的细节(段类型,绕组规则和协调).
SerializeShapes GUI
用于演示存储和恢复形状的GUI.
>单击椭圆(Ellipse2D),Rectangle(Rectangle2D)或Face(Area)按钮几次.
>退出GUI.形状将被序列化到磁盘.
>重新启动GUI.上次随机绘制的形状将从磁盘&重新出现在GUI中.
所选形状将以绿色填充,其他形状将以红色填充.
package serialize2d;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
/** A GUI to make it easy to add/remove shapes from a canvas.
It should persist the shapes between runs. */
public class SerializeShapes {
JPanel ui;
JPanel shapePanel;
Random rand;
JPanel shapeCanvas;
DefaultListModel<Shape> allShapesModel;
ListSelectionModel shapeSelectionModel;
RenderingHints renderingHints;
SerializeShapes() {
initUI();
}
public void initUI() {
if (ui != null) {
return;
}
renderingHints = new RenderingHints(RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_ENABLE);
renderingHints.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
renderingHints.put(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
renderingHints.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE);
ui = new JPanel(new BorderLayout(4,4));
ui.setBorder(new EmptyBorder(4,4,4));
JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER,4));
ui.add(controls,BorderLayout.PAGE_START);
shapeCanvas = new ShapeCanvas();
ui.add(shapeCanvas);
rand = new Random();
allShapesModel = new DefaultListModel<Shape>();
JList<Shape> allShapes = new JList<Shape>(allShapesModel);
allShapes.setCellRenderer(new ShapeListCellRenderer());
shapeSelectionModel = allShapes.getSelectionModel();
shapeSelectionModel.setSelectionMode(
ListSelectionModel.SINGLE_SELECTION);
ListSelectionListener shapesSelectionListener
= new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
shapeCanvas.repaint();
}
};
allShapes.addListSelectionListener(shapesSelectionListener);
JScrollPane shapesScroll = new JScrollPane(
allShapes,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
);
// TODO fix this hack..
shapesScroll.getViewport().setPreferredSize(new Dimension(60,200));
ui.add(shapesScroll,BorderLayout.LINE_START);
Action addEllipse = new AbstractAction("Ellipse") {
@Override
public void actionPerformed(ActionEvent e) {
int w = rand.nextInt(100) + 10;
int h = rand.nextInt(100) + 10;
int x = rand.nextInt(shapeCanvas.getWidth() - w);
int y = rand.nextInt(shapeCanvas.getHeight() - h);
Ellipse2D ellipse = new Ellipse2D.Double(x,y,w,h);
addShape(ellipse);
}
};
addEllipse.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_E);
Action addRectangle = new AbstractAction("Rectangle") {
@Override
public void actionPerformed(ActionEvent e) {
int w = rand.nextInt(100) + 10;
int h = rand.nextInt(100) + 10;
int x = rand.nextInt(shapeCanvas.getWidth() - w);
int y = rand.nextInt(shapeCanvas.getHeight() - h);
Rectangle2D rectangle = new Rectangle2D.Double(x,h);
addShape(rectangle);
}
};
addRectangle.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_R);
final int faceStart = 128513;
final int faceEnd = 128528;
final int diff = faceEnd - faceStart;
StringBuilder sb = new StringBuilder();
for (int count = faceStart; count <= faceEnd; count++) {
sb.append(Character.tochars(count));
}
final String s = sb.toString();
Vector<Font> compatibleFontList = new Vector<Font>();
GraphicsEnvironment ge
= GraphicsEnvironment.getLocalGraphicsEnvironment();
Font[] fonts = ge.getAllFonts();
for (Font font : fonts) {
if (font.canDisplayUpTo(s) < 0) {
compatibleFontList.add(font);
}
}
JComboBox fontChooser = new JComboBox(compatibleFontList);
ListCellRenderer fontRenderer = new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(
JList list,Object value,int index,boolean isSelected,boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(
list,value,index,isSelected,cellHasFocus);
JLabel l = (JLabel) c;
Font font = (Font) value;
l.setText(font.getName());
return l;
}
};
fontChooser.setRenderer(fontRenderer);
final ComboBoxModel<Font> fontModel = fontChooser.getModel();
BufferedImage bi = new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
final FontRenderContext fontRenderContext = g.getFontRenderContext();
Action addFace = new AbstractAction("Face") {
@Override
public void actionPerformed(ActionEvent e) {
int codepoint = faceStart + rand.nextInt(diff);
String text = new String(Character.tochars(codepoint));
Font font = (Font) fontModel.getSelectedItem();
Area area = new Area(
font.deriveFont(80f).
createGlyphVector(fontRenderContext,text).
getOutline());
Rectangle bounds = area.getBounds();
float x = rand.nextInt(
shapeCanvas.getWidth() - bounds.width) - bounds.x;
float y = rand.nextInt(
shapeCanvas.getHeight() - bounds.height) - bounds.y;
AffineTransform move = AffineTransform.
getTranslateInstance(x,y);
area.transform(move);
addShape(area);
}
};
addFace.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_F);
Action delete = new AbstractAction("Delete") {
@Override
public void actionPerformed(ActionEvent e) {
int idx = shapeSelectionModel.getMinSelectionIndex();
if (idx < 0) {
JOptionPane.showMessageDialog(
ui,"Select a shape to delete","Select a Shape",JOptionPane.ERROR_MESSAGE);
} else {
allShapesModel.removeElementAt(idx);
shapeCanvas.repaint();
}
}
};
delete.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_D);
controls.add(new JButton(addEllipse));
controls.add(new JButton(addRectangle));
controls.add(new JButton(addFace));
controls.add(fontChooser);
controls.add(new JButton(delete));
try {
ArrayList<Shape> shapes = deserializeShapes();
for (Shape shape : shapes) {
allShapesModel.addElement(shape);
}
} catch (Exception ex) {
System.err.println("If first launch,this is as expected!");
ex.printStackTrace();
}
}
private void addShape(Shape shape) {
allShapesModel.addElement(shape);
int size = allShapesModel.getSize() - 1;
shapeSelectionModel.addSelectionInterval(size,size);
}
class ShapeCanvas extends JPanel {
ShapeCanvas() {
setBackground(Color.WHITE);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHints(renderingHints);
Stroke stroke = new BasicStroke(1.5f);
g2.setStroke(stroke);
int idx = shapeSelectionModel.getMinSelectionIndex();
Shape selectedShape = null;
if (idx > -1) {
selectedShape = allShapesModel.get(idx);
}
Enumeration en = allShapesModel.elements();
while (en.hasMoreElements()) {
Shape shape = (Shape) en.nextElement();
if (shape.equals(selectedShape)) {
g2.setColor(new Color(0,255,191));
} else {
g2.setColor(new Color(255,191));
}
g2.fill(shape);
g2.setColor(new Color(0,224));
g2.draw(shape);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(500,300);
}
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
SerializeShapes se = new SerializeShapes();
JFrame f = new JFrame("Serialize Shapes");
f.addWindowListener(new SerializeWindowListener(se));
f.setDefaultCloSEOperation(JFrame.DO_NOTHING_ON_CLOSE);
f.setContentPane(se.getUI());
f.setResizable(false);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
public void serializeShapes() throws FileNotFoundException {
ArrayList<Shape> shapes
= new ArrayList<Shape>();
Enumeration en = allShapesModel.elements();
while (en.hasMoreElements()) {
Shape shape = (Shape) en.nextElement();
shapes.add(shape);
}
ShapeIO.serializeShapes(shapes,this.getClass());
try {
Desktop.getDesktop().open(
ShapeIO.getSerializeFile(this.getClass()));
} catch (Exception e) {
e.printStackTrace();
}
}
public ArrayList<Shape> deserializeShapes() throws FileNotFoundException {
return ShapeIO.deserializeShapes(this.getClass());
}
class ShapeListCellRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(
JList<? extends Object> list,boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list,cellHasFocus);
JLabel l = (JLabel) c;
Shape shape = (Shape) value;
ShapeIcon icon = new ShapeIcon(shape,40);
l.setIcon(icon);
l.setText("");
return l;
}
}
class ShapeIcon implements Icon {
Shape shape;
int size;
ShapeIcon(Shape shape,int size) {
this.shape = shape;
this.size = size;
}
@Override
public void paintIcon(Component c,Graphics g,int x,int y) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHints(renderingHints);
Rectangle bounds = shape.getBounds();
int xOff = -bounds.x;
int yOff = -bounds.y;
double xRatio = (double) bounds.width / (double) size;
double yRatio = (double) bounds.height / (double) size;
double ratio = xRatio > yRatio ? xRatio : yRatio;
AffineTransform scale = AffineTransform.getScaleInstance(1 / ratio,1 / ratio);
AffineTransform shift = AffineTransform.getTranslateInstance(xOff,yOff);
AffineTransform totalTransform = new AffineTransform();
totalTransform.concatenate(scale);
totalTransform.concatenate(shift);
Area b = new Area(shape).createTransformedArea(totalTransform);
bounds = b.getBounds();
g2.setColor(Color.BLACK);
g2.fill(b);
}
@Override
public int getIconWidth() {
return size;
}
@Override
public int getIconHeight() {
return size;
}
}
}
class SerializeWindowListener extends WindowAdapter {
SerializeShapes serializeShapes;
SerializeWindowListener(SerializeShapes serializeShapes) {
this.serializeShapes = serializeShapes;
}
@Override
public void windowClosing(WindowEvent e) {
try {
serializeShapes.serializeShapes();
} catch (FileNotFoundException ex) {
ex.printStackTrace();
System.exit(1);
}
System.exit(0);
}
}
ShapeIO
对/从XML执行I / O.
package serialize2d;
import java.awt.Shape;
import java.beans.*;
import java.io.*;
import java.util.ArrayList;
public class ShapeIO {
/** Save the list of shapes to the file system. */
public static void serializeShapes(
ArrayList<Shape> shapes,Class serializeClass)
throws FileNotFoundException {
File f = getSerializeFile(serializeClass);
XMLEncoder xmle = new XMLEncoder(new FileOutputStream(f));
ArrayList<PathBean> pathSegmentsCollection = new ArrayList<>();
for (Shape shape : shapes) {
ArrayList<PathSegment> pathSegments =
BeanConverter.getSegmentsFromShape(shape);
PathBean as = new PathBean(pathSegments);
pathSegmentsCollection.add(as);
}
xmle.writeObject(pathSegmentsCollection);
xmle.flush();
xmle.close();
}
/** Load the list of shapes from the file system. */
public static ArrayList<Shape> deserializeShapes(Class serializeClass)
throws FileNotFoundException {
File f = getSerializeFile(serializeClass);
XMLDecoder xmld = new XMLDecoder(new FileInputStream(f));
ArrayList<PathBean> pathSegmentsCollection
= (ArrayList<PathBean>) xmld.readObject();
ArrayList<Shape> shapes = new ArrayList<Shape>();
for (PathBean pathSegments : pathSegmentsCollection) {
shapes.add(BeanConverter.getShapeFromSegments(pathSegments));
}
return shapes;
}
/** Provide an unique,reproducible & readable/writable path for a class. */
public static File getSerializeFile(Class serializeClass) {
File f = new File(System.getProperty("user.home"));
String[] nameParts = serializeClass.getCanonicalName().split("\\.");
f = new File(f,"java");
for (String namePart : nameParts) {
f = new File(f,namePart);
}
f.mkdirs();
f = new File(f,nameParts[nameParts.length-1] + ".xml");
return f;
}
}
BeanConverter
从Shape获取PathIterator并将其转换为可序列化的bean.将bean转换回GeneralPath.
package serialize2d;
import java.awt.Shape;
import java.awt.geom.*;
import java.util.ArrayList;
/** Utility class to convert bean to/from a Shape. */
public class BeanConverter {
/** Convert a shape to a serializable bean. */
public static ArrayList<PathSegment> getSegmentsFromShape(Shape shape) {
ArrayList<PathSegment> shapeSegments = new ArrayList<PathSegment>();
for (
PathIterator pi = shape.getPathIterator(null);
!pi.isDone();
pi.next()) {
double[] coords = new double[6];
int pathSegmentType = pi.currentSegment(coords);
int windingRule = pi.getWindingRule();
PathSegment as = new PathSegment(
pathSegmentType,windingRule,coords);
shapeSegments.add(as);
}
return shapeSegments;
}
/** Convert a serializable bean to a shape. */
public static Shape getShapeFromSegments(PathBean shapeSegments) {
GeneralPath gp = new GeneralPath();
for (PathSegment shapeSegment : shapeSegments.getPathSegments()) {
double[] coords = shapeSegment.getCoords();
int pathSegmentType = shapeSegment.getPathSegmentType();
int windingRule = shapeSegment.getWindingRule();
gp.setWindingRule(windingRule);
if (pathSegmentType == PathIterator.SEG_MOVETO) {
gp.moveTo(coords[0],coords[1]);
} else if (pathSegmentType == PathIterator.SEG_LINETO) {
gp.lineTo(coords[0],coords[1]);
} else if (pathSegmentType == PathIterator.SEG_QUADTO) {
gp.quadTo(coords[0],coords[1],coords[2],coords[3]);
} else if (pathSegmentType == PathIterator.SEG_CUBICTO) {
gp.curveTo(
coords[0],coords[3],coords[4],coords[5]);
} else if (pathSegmentType == PathIterator.SEG_CLOSE) {
gp.closePath();
} else {
System.err.println("Unexpected value! " + pathSegmentType);
}
}
return gp;
}
}
PathBean
在可序列化的bean中存储路径段的集合.
package serialize2d;
import java.awt.geom.*;
import java.util.ArrayList;
/** PathBean stores the collection of PathSegment objects
that constitute the path of a Shape. */
public class PathBean {
public ArrayList<PathSegment> pathSegments;
public PathBean() {}
public PathBean(ArrayList<PathSegment> pathSegments) {
this.pathSegments = pathSegments;
}
public ArrayList<PathSegment> getPathSegments() {
return pathSegments;
}
public void setPathSegments(ArrayList<PathSegment> pathSegments) {
this.pathSegments = pathSegments;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (PathSegment pathSegment : pathSegments) {
sb.append(" \n\t");
sb.append(pathSegment.toString());
}
sb.append(" \n");
sb.append("}");
return "PathSegments: " + sb.toString();
}
}
PathSegment
存储整个路径的一部分的路径段.
package serialize2d;
import java.util.Arrays;
/** PathSegment bean stores the detail on one segment of the path
that constitutes a Shape. */
public class PathSegment {
public int pathSegmentType;
public int windingRule;
public double[] coords;
public PathSegment() {}
public PathSegment(int pathSegmentType,int windingRule,double[] coords) {
this.pathSegmentType = pathSegmentType;
this.windingRule = windingRule;
this.coords = coords;
}
public int getPathSegmentType() {
return pathSegmentType;
}
public void setPathSegmentType(int pathSegmentType) {
this.pathSegmentType = pathSegmentType;
}
public int getWindingRule() {
return windingRule;
}
public void setWindingRule(int windingRule) {
this.windingRule = windingRule;
}
public double[] getCoords() {
return coords;
}
public void setCoords(double[] coords) {
this.coords = coords;
}
@Override
public String toString() {
String sC = (coords != null ? "" : Arrays.toString(coords));
String s = String.format(
"PathSegment: Path Segment Type:- %d \t"
+ "Winding Rule:- %d \tcoords:- %s",getPathSegmentType(),getWindingRule(),sC);
return s;
}
}
笔记
这意味着与抛光方法相反的概念证明.
> XML序列化数据变得真实快速,通常会被压缩. Zip压缩可能会使序列化对象或类文件的字节大小减少30-40%,但是可以减少80-95%的XML.在任何情况下,zip也适用于下一个点.
>对于我们希望提供序列化和恢复形状的项目类型,我们可能还要包括更多的形状细节(例如填充颜色或纹理,绘制颜色或笔画等)以及其他数据,如图像或字体.这也是Zip派上用场的地方,因为我们可以将它们全部放在同一个存档中,每个都具有最好的压缩级别(例如XML的标准,图像的标准).
可以从我的云端驱动器下载zip archive of the source files in this answer.
所以要做到这一点,仍然使用上面提到的类,我们序列化和恢复正确构造的bean,它们表示从PathIterator获得的形状部分.这些豆是:
> PathBean,它存储形成Java-2D Shape形状的PathSegment对象的集合.
> PathSegment,其存储路径的特定部分的细节(段类型,绕组规则和协调).
SerializeShapes GUI
用于演示存储和恢复形状的GUI.
>单击椭圆(Ellipse2D),Rectangle(Rectangle2D)或Face(Area)按钮几次.
>退出GUI.形状将被序列化到磁盘.
>重新启动GUI.上次随机绘制的形状将从磁盘&重新出现在GUI中.
所选形状将以绿色填充,其他形状将以红色填充.
package serialize2d; import java.awt.*; import java.awt.event.*; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.*; import java.awt.image.BufferedImage; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Random; import java.util.Vector; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.*; /** A GUI to make it easy to add/remove shapes from a canvas. It should persist the shapes between runs. */ public class SerializeShapes { JPanel ui; JPanel shapePanel; Random rand; JPanel shapeCanvas; DefaultListModel<Shape> allShapesModel; ListSelectionModel shapeSelectionModel; RenderingHints renderingHints; SerializeShapes() { initUI(); } public void initUI() { if (ui != null) { return; } renderingHints = new RenderingHints(RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_ENABLE); renderingHints.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); renderingHints.put(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY); renderingHints.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY); renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE); ui = new JPanel(new BorderLayout(4,4)); ui.setBorder(new EmptyBorder(4,4,4)); JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER,4)); ui.add(controls,BorderLayout.PAGE_START); shapeCanvas = new ShapeCanvas(); ui.add(shapeCanvas); rand = new Random(); allShapesModel = new DefaultListModel<Shape>(); JList<Shape> allShapes = new JList<Shape>(allShapesModel); allShapes.setCellRenderer(new ShapeListCellRenderer()); shapeSelectionModel = allShapes.getSelectionModel(); shapeSelectionModel.setSelectionMode( ListSelectionModel.SINGLE_SELECTION); ListSelectionListener shapesSelectionListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { shapeCanvas.repaint(); } }; allShapes.addListSelectionListener(shapesSelectionListener); JScrollPane shapesScroll = new JScrollPane( allShapes,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); // TODO fix this hack.. shapesScroll.getViewport().setPreferredSize(new Dimension(60,200)); ui.add(shapesScroll,BorderLayout.LINE_START); Action addEllipse = new AbstractAction("Ellipse") { @Override public void actionPerformed(ActionEvent e) { int w = rand.nextInt(100) + 10; int h = rand.nextInt(100) + 10; int x = rand.nextInt(shapeCanvas.getWidth() - w); int y = rand.nextInt(shapeCanvas.getHeight() - h); Ellipse2D ellipse = new Ellipse2D.Double(x,y,w,h); addShape(ellipse); } }; addEllipse.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_E); Action addRectangle = new AbstractAction("Rectangle") { @Override public void actionPerformed(ActionEvent e) { int w = rand.nextInt(100) + 10; int h = rand.nextInt(100) + 10; int x = rand.nextInt(shapeCanvas.getWidth() - w); int y = rand.nextInt(shapeCanvas.getHeight() - h); Rectangle2D rectangle = new Rectangle2D.Double(x,h); addShape(rectangle); } }; addRectangle.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_R); final int faceStart = 128513; final int faceEnd = 128528; final int diff = faceEnd - faceStart; StringBuilder sb = new StringBuilder(); for (int count = faceStart; count <= faceEnd; count++) { sb.append(Character.tochars(count)); } final String s = sb.toString(); Vector<Font> compatibleFontList = new Vector<Font>(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); Font[] fonts = ge.getAllFonts(); for (Font font : fonts) { if (font.canDisplayUpTo(s) < 0) { compatibleFontList.add(font); } } JComboBox fontChooser = new JComboBox(compatibleFontList); ListCellRenderer fontRenderer = new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent( JList list,Object value,int index,boolean isSelected,boolean cellHasFocus) { Component c = super.getListCellRendererComponent( list,value,index,isSelected,cellHasFocus); JLabel l = (JLabel) c; Font font = (Font) value; l.setText(font.getName()); return l; } }; fontChooser.setRenderer(fontRenderer); final ComboBoxModel<Font> fontModel = fontChooser.getModel(); BufferedImage bi = new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB); Graphics2D g = bi.createGraphics(); final FontRenderContext fontRenderContext = g.getFontRenderContext(); Action addFace = new AbstractAction("Face") { @Override public void actionPerformed(ActionEvent e) { int codepoint = faceStart + rand.nextInt(diff); String text = new String(Character.tochars(codepoint)); Font font = (Font) fontModel.getSelectedItem(); Area area = new Area( font.deriveFont(80f). createGlyphVector(fontRenderContext,text). getOutline()); Rectangle bounds = area.getBounds(); float x = rand.nextInt( shapeCanvas.getWidth() - bounds.width) - bounds.x; float y = rand.nextInt( shapeCanvas.getHeight() - bounds.height) - bounds.y; AffineTransform move = AffineTransform. getTranslateInstance(x,y); area.transform(move); addShape(area); } }; addFace.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_F); Action delete = new AbstractAction("Delete") { @Override public void actionPerformed(ActionEvent e) { int idx = shapeSelectionModel.getMinSelectionIndex(); if (idx < 0) { JOptionPane.showMessageDialog( ui,"Select a shape to delete","Select a Shape",JOptionPane.ERROR_MESSAGE); } else { allShapesModel.removeElementAt(idx); shapeCanvas.repaint(); } } }; delete.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_D); controls.add(new JButton(addEllipse)); controls.add(new JButton(addRectangle)); controls.add(new JButton(addFace)); controls.add(fontChooser); controls.add(new JButton(delete)); try { ArrayList<Shape> shapes = deserializeShapes(); for (Shape shape : shapes) { allShapesModel.addElement(shape); } } catch (Exception ex) { System.err.println("If first launch,this is as expected!"); ex.printStackTrace(); } } private void addShape(Shape shape) { allShapesModel.addElement(shape); int size = allShapesModel.getSize() - 1; shapeSelectionModel.addSelectionInterval(size,size); } class ShapeCanvas extends JPanel { ShapeCanvas() { setBackground(Color.WHITE); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHints(renderingHints); Stroke stroke = new BasicStroke(1.5f); g2.setStroke(stroke); int idx = shapeSelectionModel.getMinSelectionIndex(); Shape selectedShape = null; if (idx > -1) { selectedShape = allShapesModel.get(idx); } Enumeration en = allShapesModel.elements(); while (en.hasMoreElements()) { Shape shape = (Shape) en.nextElement(); if (shape.equals(selectedShape)) { g2.setColor(new Color(0,255,191)); } else { g2.setColor(new Color(255,191)); } g2.fill(shape); g2.setColor(new Color(0,224)); g2.draw(shape); } } @Override public Dimension getPreferredSize() { return new Dimension(500,300); } } public JComponent getUI() { return ui; } public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { SerializeShapes se = new SerializeShapes(); JFrame f = new JFrame("Serialize Shapes"); f.addWindowListener(new SerializeWindowListener(se)); f.setDefaultCloSEOperation(JFrame.DO_NOTHING_ON_CLOSE); f.setContentPane(se.getUI()); f.setResizable(false); f.pack(); f.setLocationByPlatform(true); f.setVisible(true); } }; SwingUtilities.invokeLater(r); } public void serializeShapes() throws FileNotFoundException { ArrayList<Shape> shapes = new ArrayList<Shape>(); Enumeration en = allShapesModel.elements(); while (en.hasMoreElements()) { Shape shape = (Shape) en.nextElement(); shapes.add(shape); } ShapeIO.serializeShapes(shapes,this.getClass()); try { Desktop.getDesktop().open( ShapeIO.getSerializeFile(this.getClass())); } catch (Exception e) { e.printStackTrace(); } } public ArrayList<Shape> deserializeShapes() throws FileNotFoundException { return ShapeIO.deserializeShapes(this.getClass()); } class ShapeListCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent( JList<? extends Object> list,boolean cellHasFocus) { Component c = super.getListCellRendererComponent(list,cellHasFocus); JLabel l = (JLabel) c; Shape shape = (Shape) value; ShapeIcon icon = new ShapeIcon(shape,40); l.setIcon(icon); l.setText(""); return l; } } class ShapeIcon implements Icon { Shape shape; int size; ShapeIcon(Shape shape,int size) { this.shape = shape; this.size = size; } @Override public void paintIcon(Component c,Graphics g,int x,int y) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHints(renderingHints); Rectangle bounds = shape.getBounds(); int xOff = -bounds.x; int yOff = -bounds.y; double xRatio = (double) bounds.width / (double) size; double yRatio = (double) bounds.height / (double) size; double ratio = xRatio > yRatio ? xRatio : yRatio; AffineTransform scale = AffineTransform.getScaleInstance(1 / ratio,1 / ratio); AffineTransform shift = AffineTransform.getTranslateInstance(xOff,yOff); AffineTransform totalTransform = new AffineTransform(); totalTransform.concatenate(scale); totalTransform.concatenate(shift); Area b = new Area(shape).createTransformedArea(totalTransform); bounds = b.getBounds(); g2.setColor(Color.BLACK); g2.fill(b); } @Override public int getIconWidth() { return size; } @Override public int getIconHeight() { return size; } } } class SerializeWindowListener extends WindowAdapter { SerializeShapes serializeShapes; SerializeWindowListener(SerializeShapes serializeShapes) { this.serializeShapes = serializeShapes; } @Override public void windowClosing(WindowEvent e) { try { serializeShapes.serializeShapes(); } catch (FileNotFoundException ex) { ex.printStackTrace(); System.exit(1); } System.exit(0); } }
ShapeIO
对/从XML执行I / O.
package serialize2d; import java.awt.Shape; import java.beans.*; import java.io.*; import java.util.ArrayList; public class ShapeIO { /** Save the list of shapes to the file system. */ public static void serializeShapes( ArrayList<Shape> shapes,Class serializeClass) throws FileNotFoundException { File f = getSerializeFile(serializeClass); XMLEncoder xmle = new XMLEncoder(new FileOutputStream(f)); ArrayList<PathBean> pathSegmentsCollection = new ArrayList<>(); for (Shape shape : shapes) { ArrayList<PathSegment> pathSegments = BeanConverter.getSegmentsFromShape(shape); PathBean as = new PathBean(pathSegments); pathSegmentsCollection.add(as); } xmle.writeObject(pathSegmentsCollection); xmle.flush(); xmle.close(); } /** Load the list of shapes from the file system. */ public static ArrayList<Shape> deserializeShapes(Class serializeClass) throws FileNotFoundException { File f = getSerializeFile(serializeClass); XMLDecoder xmld = new XMLDecoder(new FileInputStream(f)); ArrayList<PathBean> pathSegmentsCollection = (ArrayList<PathBean>) xmld.readObject(); ArrayList<Shape> shapes = new ArrayList<Shape>(); for (PathBean pathSegments : pathSegmentsCollection) { shapes.add(BeanConverter.getShapeFromSegments(pathSegments)); } return shapes; } /** Provide an unique,reproducible & readable/writable path for a class. */ public static File getSerializeFile(Class serializeClass) { File f = new File(System.getProperty("user.home")); String[] nameParts = serializeClass.getCanonicalName().split("\\."); f = new File(f,"java"); for (String namePart : nameParts) { f = new File(f,namePart); } f.mkdirs(); f = new File(f,nameParts[nameParts.length-1] + ".xml"); return f; } }
BeanConverter
从Shape获取PathIterator并将其转换为可序列化的bean.将bean转换回GeneralPath.
package serialize2d; import java.awt.Shape; import java.awt.geom.*; import java.util.ArrayList; /** Utility class to convert bean to/from a Shape. */ public class BeanConverter { /** Convert a shape to a serializable bean. */ public static ArrayList<PathSegment> getSegmentsFromShape(Shape shape) { ArrayList<PathSegment> shapeSegments = new ArrayList<PathSegment>(); for ( PathIterator pi = shape.getPathIterator(null); !pi.isDone(); pi.next()) { double[] coords = new double[6]; int pathSegmentType = pi.currentSegment(coords); int windingRule = pi.getWindingRule(); PathSegment as = new PathSegment( pathSegmentType,windingRule,coords); shapeSegments.add(as); } return shapeSegments; } /** Convert a serializable bean to a shape. */ public static Shape getShapeFromSegments(PathBean shapeSegments) { GeneralPath gp = new GeneralPath(); for (PathSegment shapeSegment : shapeSegments.getPathSegments()) { double[] coords = shapeSegment.getCoords(); int pathSegmentType = shapeSegment.getPathSegmentType(); int windingRule = shapeSegment.getWindingRule(); gp.setWindingRule(windingRule); if (pathSegmentType == PathIterator.SEG_MOVETO) { gp.moveTo(coords[0],coords[1]); } else if (pathSegmentType == PathIterator.SEG_LINETO) { gp.lineTo(coords[0],coords[1]); } else if (pathSegmentType == PathIterator.SEG_QUADTO) { gp.quadTo(coords[0],coords[1],coords[2],coords[3]); } else if (pathSegmentType == PathIterator.SEG_CUBICTO) { gp.curveTo( coords[0],coords[3],coords[4],coords[5]); } else if (pathSegmentType == PathIterator.SEG_CLOSE) { gp.closePath(); } else { System.err.println("Unexpected value! " + pathSegmentType); } } return gp; } }
PathBean
在可序列化的bean中存储路径段的集合.
package serialize2d; import java.awt.geom.*; import java.util.ArrayList; /** PathBean stores the collection of PathSegment objects that constitute the path of a Shape. */ public class PathBean { public ArrayList<PathSegment> pathSegments; public PathBean() {} public PathBean(ArrayList<PathSegment> pathSegments) { this.pathSegments = pathSegments; } public ArrayList<PathSegment> getPathSegments() { return pathSegments; } public void setPathSegments(ArrayList<PathSegment> pathSegments) { this.pathSegments = pathSegments; } @Override public String toString() { StringBuilder sb = new StringBuilder("{"); for (PathSegment pathSegment : pathSegments) { sb.append(" \n\t"); sb.append(pathSegment.toString()); } sb.append(" \n"); sb.append("}"); return "PathSegments: " + sb.toString(); } }
PathSegment
存储整个路径的一部分的路径段.
package serialize2d; import java.util.Arrays; /** PathSegment bean stores the detail on one segment of the path that constitutes a Shape. */ public class PathSegment { public int pathSegmentType; public int windingRule; public double[] coords; public PathSegment() {} public PathSegment(int pathSegmentType,int windingRule,double[] coords) { this.pathSegmentType = pathSegmentType; this.windingRule = windingRule; this.coords = coords; } public int getPathSegmentType() { return pathSegmentType; } public void setPathSegmentType(int pathSegmentType) { this.pathSegmentType = pathSegmentType; } public int getWindingRule() { return windingRule; } public void setWindingRule(int windingRule) { this.windingRule = windingRule; } public double[] getCoords() { return coords; } public void setCoords(double[] coords) { this.coords = coords; } @Override public String toString() { String sC = (coords != null ? "" : Arrays.toString(coords)); String s = String.format( "PathSegment: Path Segment Type:- %d \t" + "Winding Rule:- %d \tcoords:- %s",getPathSegmentType(),getWindingRule(),sC); return s; } }
笔记
这意味着与抛光方法相反的概念证明.
> XML序列化数据变得真实快速,通常会被压缩. Zip压缩可能会使序列化对象或类文件的字节大小减少30-40%,但是可以减少80-95%的XML.在任何情况下,zip也适用于下一个点.
>对于我们希望提供序列化和恢复形状的项目类型,我们可能还要包括更多的形状细节(例如填充颜色或纹理,绘制颜色或笔画等)以及其他数据,如图像或字体.这也是Zip派上用场的地方,因为我们可以将它们全部放在同一个存档中,每个都具有最好的压缩级别(例如XML的标准,图像的标准).
可以从我的云端驱动器下载zip archive of the source files in this answer.