From: Frederick W. <fre...@us...> - 2012-02-01 17:59:52
|
rails/ui/swing/ORUIManager.java | 6 + rails/ui/swing/UpgradesPanel.java | 30 ++++++++- rails/ui/swing/hexmap/GUIHex.java | 7 +- rails/ui/swing/hexmap/GUITile.java | 120 ++++++++++++++++++++++++++++++------- 4 files changed, 136 insertions(+), 27 deletions(-) New commits: commit f69750118d746100e0f16eb7b1b2ebe3813810dd Author: Frederick Weld <fre...@gm...> Date: Wed Feb 1 18:54:15 2012 +0100 Rotate upgrade panel tiles to the first valid orientation The existing logic of determining validity of orientations was re-used (required some refactoring). This means that, once this logic is further improved, the upgrade panel's tile orientations will also get more accurate. The other source of complexity is that svg tile images are not centered and that image rotation by AOP result in additional borders on the bottom/right. Code that is able to handle that complexity is put to GUITile, as the former tile paint code is also there. diff --git a/rails/ui/swing/ORUIManager.java b/rails/ui/swing/ORUIManager.java index 6f7f8c6..226a8b5 100644 --- a/rails/ui/swing/ORUIManager.java +++ b/rails/ui/swing/ORUIManager.java @@ -731,7 +731,11 @@ public class ORUIManager implements DialogOwner { } } - private boolean getMustConnectRequirement (GUIHex hex,TileI tile) { + /** + * @return True if the indicated tile must be connected to some other track if + * placed on indicated hex. + */ + public boolean getMustConnectRequirement (GUIHex hex,TileI tile) { if (tile == null || hex == null) return false; return tile.getColourName().equalsIgnoreCase(Tile.YELLOW_COLOUR_NAME) // Does not apply to the current company's home hex(es) diff --git a/rails/ui/swing/UpgradesPanel.java b/rails/ui/swing/UpgradesPanel.java index 16486b5..1ac2332 100644 --- a/rails/ui/swing/UpgradesPanel.java +++ b/rails/ui/swing/UpgradesPanel.java @@ -19,11 +19,14 @@ import rails.game.action.*; import rails.game.correct.MapCorrectionAction; import rails.ui.swing.elements.ActionLabel; import rails.ui.swing.hexmap.GUIHex; +import rails.ui.swing.hexmap.GUITile; import rails.ui.swing.hexmap.HexHighlightMouseListener; import rails.ui.swing.hexmap.HexMap; public class UpgradesPanel extends Box implements MouseListener, ActionListener { private static final long serialVersionUID = 1L; + + private static final int UPGRADE_TILE_ZOOM_STEP = 10; private ORUIManager orUIManager; private List<ActionLabel> tokenLabels; @@ -61,7 +64,7 @@ public class UpgradesPanel extends Box implements MouseListener, ActionListener this.orUIManager = orUIManager; - preferredSize = new Dimension((int)Math.round(100 * (2 + Scale.getFontScale())/3), 200); + preferredSize = new Dimension((int)Math.round(110 * (2 + Scale.getFontScale())/3), 200); setSize(preferredSize); setVisible(true); @@ -220,7 +223,28 @@ public class UpgradesPanel extends Box implements MouseListener, ActionListener } private HexLabel createHexLabel(TileI tile,String toolTipHeaderLine) { - BufferedImage hexImage = getHexImage(tile.getPictureId()); + BufferedImage hexImage = null; + + //get a buffered image of the tile in the first valid orientation + GUIHex selectedGUIHex = hexMap.getSelectedHex(); + if (selectedGUIHex != null) { + GUITile tempGUITile = selectedGUIHex.createUpgradeTileIfValid ( + tile.getId(), + orUIManager.getMustConnectRequirement(selectedGUIHex, tile)); + + if (tempGUITile != null) { + //tile has been rotated to valid orientation + //get unscaled image for this orientation + hexImage = tempGUITile.getTileImage(UPGRADE_TILE_ZOOM_STEP); + } + } + + //fallback if no valid orientation exists: + //get the image in the standard orientation + if (hexImage == null) { + hexImage = getHexImage(tile.getPictureId()); + } + ImageIcon hexIcon = new ImageIcon(hexImage); // Cheap n' Easy rescaling. @@ -375,7 +399,7 @@ public class UpgradesPanel extends Box implements MouseListener, ActionListener } private BufferedImage getHexImage(int tileId) { - return GameUIManager.getImageLoader().getTile(tileId, 10); + return GameUIManager.getImageLoader().getTile(tileId, UPGRADE_TILE_ZOOM_STEP); } @Override diff --git a/rails/ui/swing/hexmap/GUIHex.java b/rails/ui/swing/hexmap/GUIHex.java index f67de31..af052cb 100644 --- a/rails/ui/swing/hexmap/GUIHex.java +++ b/rails/ui/swing/hexmap/GUIHex.java @@ -889,7 +889,12 @@ public class GUIHex implements ViewObject { return (provisionalGUITile != null); } - private GUITile createUpgradeTileIfValid (int tileId, boolean upgradeMustConnect) { + /** + * Creates an upgrade tile onto this hex without dropping it on the hex. + * This means that this hex won't consider the returned tile being part of it + * (even not on a temporary base). + */ + public GUITile createUpgradeTileIfValid (int tileId, boolean upgradeMustConnect) { GUITile t = new GUITile(tileId, this); /* Check if we can find a valid orientation of this tile */ return ( t.rotate(0, currentGUITile, upgradeMustConnect) ? t : null); diff --git a/rails/ui/swing/hexmap/GUITile.java b/rails/ui/swing/hexmap/GUITile.java index 9adfa4c..becbe56 100644 --- a/rails/ui/swing/hexmap/GUITile.java +++ b/rails/ui/swing/hexmap/GUITile.java @@ -25,7 +25,6 @@ public class GUITile { protected String tileType = null; protected int picId; - protected BufferedImage tileImage = null; protected int rotation = 0; @@ -38,7 +37,7 @@ public class GUITile { protected static ImageLoader imageLoader = GameUIManager.getImageLoader(); - protected AffineTransform af = new AffineTransform(); + protected RenderingHints renderingHints = null; public static final double DEG60 = Math.PI / 3; @@ -248,11 +247,32 @@ public class GUITile { tileScale = scale; } + private RenderingHints getTileRenderingHints() { + if (renderingHints == null) { + renderingHints = new RenderingHints(null); + renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + renderingHints.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + renderingHints.put(RenderingHints.KEY_COLOR_RENDERING, + RenderingHints.VALUE_COLOR_RENDER_QUALITY); + renderingHints.put(RenderingHints.KEY_DITHERING, + RenderingHints.VALUE_DITHER_DISABLE); + renderingHints.put(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + renderingHints.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + return renderingHints; + } + public void paintTile(Graphics2D g2, int x, int y) { int zoomStep = guiHex.getHexMap().getZoomStep(); - tileImage = imageLoader.getTile(picId, zoomStep); + BufferedImage tileImage = imageLoader.getTile(picId, zoomStep); if (tileImage != null) { @@ -262,33 +282,89 @@ public class GUITile { int yCenter = (int) Math.round(tileImage.getHeight() * SVG_Y_CENTER_LOC * tileScale); - af = AffineTransform.getRotateInstance(radians, xCenter, yCenter); + AffineTransform af = AffineTransform.getRotateInstance(radians, xCenter, yCenter); af.scale(tileScale, tileScale); - RenderingHints rh = new RenderingHints(null); - rh.put(RenderingHints.KEY_ALPHA_INTERPOLATION, - RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - rh.put(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - rh.put(RenderingHints.KEY_COLOR_RENDERING, - RenderingHints.VALUE_COLOR_RENDER_QUALITY); - rh.put(RenderingHints.KEY_DITHERING, - RenderingHints.VALUE_DITHER_DISABLE); - rh.put(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BICUBIC); - rh.put(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY); - rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - - AffineTransformOp aop = new AffineTransformOp(af, rh); + AffineTransformOp aop = new AffineTransformOp(af, + getTileRenderingHints()); g2.drawImage(tileImage, aop, x - xCenter, y - yCenter); + } else { - log.error("No image for tile "+tileId+" on hex "+guiHex.getName()); + log.error("No image for tile "+tileId+" on hex "+guiHex.getName()); } } + + /** + * Provides the image of the tile based on the zoomStep. + * tileScale is not considered for producing this image. + */ + public BufferedImage getTileImage(int zoomStep) { + + // STEP 1: GET IMAGE FROM SVG + // image not centered as there will be a bottom border to assign square bounds to the image + + BufferedImage uncenteredTileImage = imageLoader.getTile(picId, zoomStep); + if (uncenteredTileImage == null) return null; + + //svg always in NS orientation, hence wide diagonal can be directly taken from image size + int wideDiagonal = uncenteredTileImage.getWidth(); + + //narrow diagonal cannot be taken from image height due to the bottom border + int narrowDiagonal = (int)Math.round( wideDiagonal * 0.5 * Math.sqrt(3) ); + + int border = wideDiagonal - narrowDiagonal; + + // STEP 2: CENTER TILE IN IMAGE + // apply the bottom border also the left / top / right + + //center tile by translation + AffineTransform centeringAT = AffineTransform.getTranslateInstance( border, border ); + AffineTransformOp centeringATOp = new AffineTransformOp(centeringAT, null); + + //centered tile image create manually since it also needs a border on the right + BufferedImage centeredTileImage = new BufferedImage( + uncenteredTileImage.getWidth() + border * 2, + uncenteredTileImage.getHeight() + border, + uncenteredTileImage.getType()); + + centeringATOp.filter(uncenteredTileImage, centeredTileImage); + + // STEP 3: ROTATE TILE IMAGE + // feasible only now since there are enough margins to ensure tile won't exceed bounds + + double radians = baseRotation + rotation * DEG60; + int xCenter = (int) Math.round(centeredTileImage.getWidth() / 2 ); + int yCenter = (int) Math.round(centeredTileImage.getHeight() / 2 ); + + AffineTransform af = AffineTransform.getRotateInstance(radians, xCenter, yCenter); + AffineTransformOp aop = new AffineTransformOp(af, getTileRenderingHints()); + + BufferedImage rotatedTileImage = aop.filter(centeredTileImage, null); + + // STEP 4: CROP ROTATED TILE IMAGE + // rotation result will have additional borders on the right/bottom as a result of the AOP + + int croppedWidth, croppedHeight; + if (baseRotation == 0) { + //tile in NS orientation after rotation + croppedWidth = wideDiagonal; + croppedHeight = narrowDiagonal; + } else { + //tile in EW orientation after rotation + croppedWidth = narrowDiagonal; + croppedHeight = wideDiagonal; + } + BufferedImage croppedTileImage = rotatedTileImage.getSubimage( + xCenter - croppedWidth / 2, + yCenter - croppedHeight / 2, + croppedWidth, + croppedHeight ); + + return croppedTileImage; + } + public TileI getTile() { return tile; } |