Java tutorial
/* * $Id: TilesImageBlock.java 3 2008-11-18 20:02:30Z Nathaniel.Waisbrot $ Copyright (C) 2008 Nathaniel Waisbrot This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package termint.gui.graphics; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Display; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import termint.Main; import termint.gui.vt.VTElement; import termint.util.Pair; /** * Holds a VTElement->Image mapping which works with a single large source image containing many little tiles, * like the various NetHack tiles distributions. * * Note that the data in a VTElement is <i>not</i> sufficient to fully differentiate some tiles. * * @author Nathaniel Waisbrot * */ public class TilesImageBlock extends Tiles { private final Logger log = LoggerFactory.getLogger(getClass()); /** File name of the image block we're using */ private static final String imageFile = "chozo32b.bmp"; private static final String tileMappingFile = "tile-mapping.json"; /** Width in pixels of each image of {@link TilesImageBlock#imageFile} */ private static final int imageWidth = 32; /** Height in pixels of each image of {@link TilesImageBlock#imageFile} */ private static final int imageHeight = 32; /** Number of columns of sub-images contained in {@link TilesImageBlock#imageFile} */ private static final int imageBlockColumns = 40; /** Image shown when the mapping does not contain any data for the given key */ private final Image badImage; /** The large image, loaded into memory */ private Image bigImage; /** Mapping between VTElements and coordinates into the {@link TilesImageBlock#bigImage} */ protected Map<VTElement, Point> elementToImage; private final TilesTextColor textTiles; /** File in which to store the {@link TilesImageBlock#elementToImage} mapping */ private static final String dataFile = Main.localDir + "tileData"; /** * Create a new mapping * @param display the display on which to draw * @param background the background color to use for tiles */ public TilesImageBlock(Display display, Color background) { super(display, background); setTileSize(32, 32); textTiles = new TilesTextColor(display, 30, new Pair<>(32, 32)); badImage = newImage(); GC gc = getGC(badImage); gc.setForeground(display.getSystemColor(SWT.COLOR_RED)); gc.drawLine(4, 4, 24, 24); gc.drawLine(24, 4, 4, 24); // set null image to the background color nullImage = newImage(); gc = getGC(nullImage); gc.dispose(); // a little goofyness to make a transparent cursor Image cur = newImage(); gc = new GC(cur); gc.setBackground(new Color(display, 0, 0, 0x99)); gc.fillRectangle(cur.getBounds()); gc.dispose(); ImageData d = cur.getImageData(); d.alpha = 90; cur.dispose(); cur = new Image(display, d); setCursor(cur); // #### NEW STUFF ##### InputStream is = this.getClass().getResourceAsStream(imageFile); bigImage = new Image(display, is); elementToImage = loadFromJsonFile(); Iterator<Entry<VTElement, Point>> iter = elementToImage.entrySet().iterator(); while (iter.hasNext()) { Entry<VTElement, Point> e = iter.next(); setImage(e.getKey(), e.getValue()); } } /** * Assuming that the ImageData is a grid of images, each one (imageHeight * imageWidth), * extract and return the image at the given grid location. * @param x * @param y * @return */ protected Image getIconAt(int x, int y) { if (x >= imageBlockColumns) throw new ArrayIndexOutOfBoundsException( "getIconAt(" + x + "," + y + "): x exceeds max val " + imageBlockColumns); Image i = new Image(display, imageWidth, imageHeight); GC g = new GC(i); try { g.drawImage(bigImage, x * imageWidth, y * imageHeight, imageWidth, imageHeight, 0, 0, imageHeight, imageWidth); } catch (Exception e) { throw new RuntimeException("Failed to draw image from " + x + "," + y, e); } g.dispose(); return i; } /** * @see TilesImageBlock#getIconAt(int, int) * @param p * @return */ protected Image getIconAt(Point p) { return getIconAt(p.x, p.y); } protected Point indexToXY(int n) { int row = 0; while (n >= imageBlockColumns) { row++; n -= imageBlockColumns; } return new Point(n, row); } protected Image getNthIcon(int n) { Point p = indexToXY(n); return getIconAt(p.x, p.y); } /** * Set the mapping for the given VTElement to be the <i>n<super>th</super></i> image of the large image * @param element * @param index */ public void setImage(VTElement element, int index) { if (index < 0) { setImage(element, badImage); elementToImage.remove(element); } else setImage(element, indexToXY(index)); } public void setImage(VTElement element, Point xy) { setImage(element, getIconAt(xy)); elementToImage.put(element, xy); } /** * Set up a GraphicsContext that will draw on the image. Sets the font, background, etc. * @param img a blank Image to draw on * @return a GC that draws on the Image */ private GC getGC(Image img) { GC g = new GC(img); g.setBackground(background); g.fillRectangle(img.getBounds()); return g; } private final Set<VTElement> observedBadElements = new HashSet<>(); @Override public Image getImage(VTElement key) { if (key.getChar() == '@') log.debug("here"); Image img = super.getImage(key); if ((img != nullImage) || (key.isNull())) return img; if (!observedBadElements.contains(key)) { observedBadElements.add(key); log.warn("Missing image for VTElement '{}'", key.toJSON()); } return textTiles.getImage(key); } public void saveData() { saveMapping(); } @Override public void dispose() { super.dispose(); saveData(); bigImage.dispose(); badImage.dispose(); } private static final String mapfile_start_banner = "==START ::$Rev$:: =="; private static final String mapfile_end_banner = "==END=="; /** * Save the object -> tile mapping */ protected void saveMapping() { PrintWriter mapOut = null; try { mapOut = new PrintWriter(new BufferedWriter(new FileWriter(dataFile))); } catch (FileNotFoundException e) { assert false : "saveMapping: FileNotFound: " + e; } catch (IOException e) { assert false : "saveMapping: IOException: " + e; } mapOut.println(mapfile_start_banner); Iterator<Entry<VTElement, Point>> itr = elementToImage.entrySet().iterator(); while (itr.hasNext()) { Entry<VTElement, Point> e = itr.next(); mapOut.println(e.getKey().toString() + "\t" + e.getValue().x + ',' + e.getValue().y); } mapOut.println(mapfile_end_banner); mapOut.flush(); mapOut.close(); } private Map<VTElement, Point> loadFromJsonFile() { try { Map<VTElement, Point> map = new HashMap<VTElement, Point>(); InputStream is = this.getClass().getResourceAsStream(tileMappingFile); if (is != null) { JSONArray elements = new JSONArray(new JSONTokener(new InputStreamReader(is))); for (int i = 0; i < elements.length(); i++) { JSONObject jmap = elements.getJSONObject(i); VTElement element = VTElement.fromJSON(jmap.getJSONObject("vte")); Point p = new Point(jmap.getInt("x"), jmap.getInt("y")); map.put(element, p); } } return map; } catch (JSONException e) { throw new RuntimeException(e); } } }