/* * $Id$ * * Copyright (c) 2003 by Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.counters; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Window; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.StringTokenizer; import javax.swing.BoxLayout; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import VASSAL.build.module.documentation.HelpFile; import VASSAL.command.Command; import VASSAL.tools.image.ImageUtils; import VASSAL.tools.imageop.Op; /** * A trait for assigning an arbitrary shape to a {@link GamePiece} * * @see GamePiece#getShape */ public class NonRectangular extends Decorator implements EditablePiece { public static final String ID = "nonRect;"; private static HashMap<String,Shape> shapeCache = new HashMap<String,Shape>(); private String type; private Shape shape; public NonRectangular() { this(ID, null); } public NonRectangular(String type, GamePiece inner) { mySetType(type); setInner(inner); } public void mySetState(String newState) { } public String myGetState() { return ""; } public String myGetType() { return type; } protected KeyCommand[] myGetKeyCommands() { return new KeyCommand[0]; } public Command myKeyEvent(KeyStroke stroke) { return null; } public void draw(Graphics g, int x, int y, Component obs, double zoom) { piece.draw(g, x, y, obs, zoom); } public Rectangle boundingBox() { return piece.boundingBox(); } public Shape getShape() { return shape != null ? shape : piece.getShape(); } public String getName() { return piece.getName(); } public String getDescription() { return "Non-Rectangular"; } public void mySetType(String type) { this.type = type; final String shapeSpec = type.substring(ID.length()); shape = buildPath(shapeSpec); } private Shape buildPath(String spec) { Shape sh = shapeCache.get(spec); if (sh == null) { final GeneralPath path = new GeneralPath(); final StringTokenizer st = new StringTokenizer(spec, ","); if (st.hasMoreTokens()) { while (st.hasMoreTokens()) { final String token = st.nextToken(); switch (token.charAt(0)) { case 'c': path.closePath(); break; case 'm': path.moveTo(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken())); break; case 'l': path.lineTo(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken())); break; } } sh = new Area(path); shapeCache.put(spec, sh); } } return sh; } public HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("NonRectangular.htm"); } public PieceEditor getEditor() { return new Ed(this); } private class Ed implements PieceEditor { private Shape shape; private JPanel controls; private Ed(NonRectangular p) { shape = p.shape; controls = new JPanel(); controls.setLayout(new BoxLayout(controls, BoxLayout.X_AXIS)); final JPanel shapePanel = new JPanel() { private static final long serialVersionUID = 1L; public void paint(Graphics g) { final Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.white); g2d.fillRect(0, 0, getWidth(), getHeight()); if (shape != null) { g2d.translate(getWidth() / 2, getHeight() / 2); g2d.setColor(Color.black); g2d.fill(shape); } } public Dimension getPreferredSize() { final Dimension d = shape == null ? new Dimension(60, 60) : shape.getBounds().getSize(); d.width = Math.max(d.width, 60); d.height = Math.max(d.height, 60); return d; } }; controls.add(shapePanel); final ImagePicker picker = new ImagePicker() { private static final long serialVersionUID = 1L; public void setImageName(String name) { super.setImageName(name); final Image img = Op.load(name).getImage(); if (img != null) setShapeFromImage(img); } }; picker.setBorder(new TitledBorder("Use image shape")); controls.add(picker); } public void setShapeFromImage(Image im) { controls.getTopLevelAncestor() .setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); final BufferedImage bi = ImageUtils.toBufferedImage(im); final int w = bi.getWidth(); final int h = bi.getHeight(); final int[] pixels = bi.getRGB(0, 0, w, h, new int[w*h], 0, w); // build the outline in strips final Area outline = new Area(); for (int y = 0; y < h; ++y) { int left = -1; for (int x = 0; x < w; ++x) { if (((pixels[x + y*w] >>> 24) & 0xff) > 0) { if (left < 0) { left = x; } } else if (left > -1) { outline.add(new Area(new Rectangle(left, y, x-left, 1))); left = -1; } } if (left > -1) { outline.add(new Area(new Rectangle(left, y, w-left, 1))); } } // FIXME: should be 2.0 to avoid integer arithemtic? shape = AffineTransform.getTranslateInstance(-w / 2, -h / 2) .createTransformedShape(outline); final Window wd = SwingUtilities.getWindowAncestor(controls); if (wd != null) wd.pack(); controls.getTopLevelAncestor().setCursor(null); } public Component getControls() { return controls; } public String getType() { final StringBuilder buffer = new StringBuilder(); if (shape != null) { final PathIterator it = shape.getPathIterator(new AffineTransform()); final float[] pts = new float[6]; while (!it.isDone()) { switch (it.currentSegment(pts)) { case PathIterator.SEG_MOVETO: buffer.append('m') .append(',') .append(Math.round(pts[0])) .append(',') .append(Math.round(pts[1])); break; case PathIterator.SEG_LINETO: case PathIterator.SEG_CUBICTO: case PathIterator.SEG_QUADTO: buffer.append('l') .append(',') .append(Math.round(pts[0])) .append(',') .append(Math.round(pts[1])); break; case PathIterator.SEG_CLOSE: buffer.append('c'); break; } it.next(); if (!it.isDone()) { buffer.append(','); } } } return ID + buffer.toString(); } public String getState() { return ""; } } }