package games.strategy.triplea.ui.screen; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import games.strategy.engine.data.GameData; import games.strategy.thread.LockUtil; import games.strategy.triplea.ui.mapdata.MapData; import games.strategy.triplea.ui.screen.drawable.DrawableComparator; import games.strategy.triplea.ui.screen.drawable.IDrawable; import games.strategy.triplea.util.Stopwatch; import games.strategy.ui.Util; public class Tile { public static final LockUtil S_TILE_LOCKUTIL = LockUtil.INSTANCE; private static final boolean DRAW_DEBUG = false; private static final Logger s_logger = Logger.getLogger(Tile.class.getName()); // allow the gc to implement memory management private SoftReference<Image> m_imageRef; private boolean m_isDirty = true; private final Rectangle m_bounds; private final int m_x; private final int m_y; private final double m_scale; private final Lock m_lock = new ReentrantLock(); private final List<IDrawable> m_contents = new ArrayList<>(); public Tile(final Rectangle bounds, final int x, final int y, final double scale) { // s_logger.log(Level.FINER, "Tile created for:" + bounds); m_bounds = bounds; m_x = x; m_y = y; m_scale = scale; } public boolean isDirty() { acquireLock(); try { return m_isDirty || m_imageRef == null || m_imageRef.get() == null; } finally { releaseLock(); } } public void acquireLock() { S_TILE_LOCKUTIL.acquireLock(m_lock); } public void releaseLock() { S_TILE_LOCKUTIL.releaseLock(m_lock); } public Image getImage(final GameData data, final MapData mapData) { acquireLock(); try { if (m_imageRef == null) { m_imageRef = new SoftReference<>(createBlankImage()); m_isDirty = true; } Image image = m_imageRef.get(); if (image == null) { image = createBlankImage(); m_imageRef = new SoftReference<>(image); m_isDirty = true; } if (m_isDirty) { final Graphics2D g = (Graphics2D) image.getGraphics(); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); draw(g, data, mapData); g.dispose(); } return image; } finally { releaseLock(); } } private BufferedImage createBlankImage() { return Util.createImage((int) (m_bounds.getWidth() * m_scale), (int) (m_bounds.getHeight() * m_scale), false); } /** * This image may be null, and it may not reflect our current drawables. Use getImage() to get * a correct image * * @return the image we currently have. */ public Image getRawImage() { if (m_imageRef == null) { return null; } return m_imageRef.get(); } private void draw(final Graphics2D g, final GameData data, final MapData mapData) { final AffineTransform unscaled = g.getTransform(); AffineTransform scaled; if (m_scale != 1) { scaled = new AffineTransform(); scaled.scale(m_scale, m_scale); g.setTransform(scaled); } else { scaled = unscaled; } final Stopwatch stopWatch = new Stopwatch(s_logger, Level.FINEST, "Drawing Tile at" + m_bounds); // clear g.setColor(Color.BLACK); g.fill(new Rectangle(0, 0, TileManager.TILE_SIZE, TileManager.TILE_SIZE)); Collections.sort(m_contents, new DrawableComparator()); final Iterator<IDrawable> iter = m_contents.iterator(); while (iter.hasNext()) { final IDrawable drawable = iter.next(); drawable.draw(m_bounds, data, g, mapData, unscaled, scaled); } m_isDirty = false; // draw debug graphics if (DRAW_DEBUG) { g.setColor(Color.PINK); final Rectangle r = new Rectangle(1, 1, TileManager.TILE_SIZE - 2, TileManager.TILE_SIZE - 2); g.setStroke(new BasicStroke(1)); g.draw(r); g.setFont(new Font("Ariel", Font.BOLD, 25)); g.drawString(m_x + " " + m_y, 40, 40); } stopWatch.done(); } public void addDrawables(final Collection<IDrawable> drawables) { acquireLock(); try { m_contents.addAll(drawables); m_isDirty = true; } finally { releaseLock(); } } public void addDrawable(final IDrawable d) { acquireLock(); try { m_contents.add(d); m_isDirty = true; } finally { releaseLock(); } } public void removeDrawable(final IDrawable d) { acquireLock(); try { m_contents.remove(d); m_isDirty = true; } finally { releaseLock(); } } public void removeDrawables(final Collection<IDrawable> c) { acquireLock(); try { m_contents.removeAll(c); m_isDirty = true; } finally { releaseLock(); } } public void clear() { acquireLock(); try { m_contents.clear(); m_isDirty = true; } finally { releaseLock(); } } public List<IDrawable> getDrawables() { acquireLock(); try { return new ArrayList<>(m_contents); } finally { releaseLock(); } } public Rectangle getBounds() { return m_bounds; } public int getX() { return m_x; } public int getY() { return m_y; } }