termint.gui.graphics.TilesImageBlock.java Source code

Java tutorial

Introduction

Here is the source code for termint.gui.graphics.TilesImageBlock.java

Source

/*
 *  $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);
        }
    }
}