package com.kreative.paint.document.tile; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Shape; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.List; import com.kreative.paint.document.undo.Atom; import com.kreative.paint.document.undo.History; import com.kreative.paint.document.undo.Recordable; public class Tile implements Cloneable, Recordable, PaintSurface { private int x; private int y; private int width; private int height; private int matte; private BufferedImage image; private History history; private List<TileListener> listeners; public Tile(int x, int y, int width, int height, int matte) { this.x = x; this.y = y; this.width = width; this.height = height; this.matte = matte; this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); int[] rgb = new int[width * height]; for (int i = 0; i < rgb.length; i++) rgb[i] = matte; this.image.setRGB(0, 0, width, height, rgb, 0, width); this.history = null; this.listeners = new ArrayList<TileListener>(); } private Tile(Tile o) { this.x = o.x; this.y = o.y; this.width = o.width; this.height = o.height; this.matte = o.matte; this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); int[] rgb = new int[width * height]; o.image.getRGB(0, 0, width, height, rgb, 0, width); this.image.setRGB(0, 0, width, height, rgb, 0, width); this.history = null; this.listeners = new ArrayList<TileListener>(); } @Override public Tile clone() { return new Tile(this); } @Override public History getHistory() { return history; } @Override public void setHistory(History history) { this.history = history; } public void addTileListener(TileListener l) { listeners.add(l); } public void removeTileListener(TileListener l) { listeners.remove(l); } public TileListener[] getTileListeners() { return listeners.toArray(new TileListener[listeners.size()]); } protected void notifyTileListeners(int id) { if (listeners.isEmpty()) return; TileEvent e = new TileEvent(id, this); switch (id) { case TileEvent.TILE_LOCATION_CHANGED: for (TileListener l : listeners) l.tileLocationChanged(e); break; case TileEvent.TILE_MATTE_CHANGED: for (TileListener l : listeners) l.tileMatteChanged(e); break; case TileEvent.TILE_CONTENT_CHANGED: for (TileListener l : listeners) l.tileContentChanged(e); break; } } public int getX() { return x; } public int getY() { return y; } public Point getLocation() { return new Point(x, y); } private static class LocationAtom implements Atom { private Tile t; private int oldX, oldY; private int newX, newY; public LocationAtom(Tile t, int newX, int newY) { this.t = t; this.oldX = t.x; this.oldY = t.y; this.newX = newX; this.newY = newY; } @Override public boolean canBuildUpon(Atom prev) { return (prev instanceof LocationAtom) && (((LocationAtom)prev).t == this.t); } @Override public Atom buildUpon(Atom prev) { this.oldX = ((LocationAtom)prev).oldX; this.oldY = ((LocationAtom)prev).oldY; return this; } @Override public void redo() { t.x = newX; t.y = newY; t.notifyTileListeners(TileEvent.TILE_LOCATION_CHANGED); } @Override public void undo() { t.x = oldX; t.y = oldY; t.notifyTileListeners(TileEvent.TILE_LOCATION_CHANGED); } } public void setX(int x) { if (this.x == x) return; if (history != null) history.add(new LocationAtom(this, x, y)); this.x = x; this.notifyTileListeners(TileEvent.TILE_LOCATION_CHANGED); } public void setY(int y) { if (this.y == y) return; if (history != null) history.add(new LocationAtom(this, x, y)); this.y = y; this.notifyTileListeners(TileEvent.TILE_LOCATION_CHANGED); } public void setLocation(Point p) { if (this.x == p.x && this.y == p.y) return; if (history != null) history.add(new LocationAtom(this, p.x, p.y)); this.x = p.x; this.y = p.y; this.notifyTileListeners(TileEvent.TILE_LOCATION_CHANGED); } public int getWidth() { return width; } public int getHeight() { return height; } public Dimension getSize() { return new Dimension(width, height); } public int getMatte() { return matte; } private static class MatteAtom implements Atom { private Tile t; private int oldMatte; private int newMatte; public MatteAtom(Tile t, int newMatte) { this.t = t; this.oldMatte = t.matte; this.newMatte = newMatte; } @Override public boolean canBuildUpon(Atom prev) { return (prev instanceof MatteAtom) && (((MatteAtom)prev).t == this.t); } @Override public Atom buildUpon(Atom prev) { this.oldMatte = ((MatteAtom)prev).oldMatte; return this; } @Override public void redo() { t.matte = newMatte; t.notifyTileListeners(TileEvent.TILE_MATTE_CHANGED); } @Override public void undo() { t.matte = oldMatte; t.notifyTileListeners(TileEvent.TILE_MATTE_CHANGED); } } public void setMatte(int matte) { if (this.matte == matte) return; if (history != null) history.add(new MatteAtom(this, matte)); this.matte = matte; this.notifyTileListeners(TileEvent.TILE_MATTE_CHANGED); } public BufferedImage getImage() { return image; } public void paint(Graphics2D g) { g.drawImage(image, null, x, y); } public void paint(Graphics2D g, int x, int y) { g.drawImage(image, null, this.x + x, this.y + y); } @Override public int getMinX() { return x; } @Override public int getMinY() { return y; } @Override public int getMaxX() { return x + width; } @Override public int getMaxY() { return y + height; } @Override public boolean contains(int x, int y) { return x >= this.x && y >= this.y && x < this.x + this.width && y < this.y + this.height; } @Override public boolean contains(int x, int y, int width, int height) { return x >= this.x && y >= this.y && x + width <= this.x + this.width && y + height <= this.y + this.height; } @Override public int getRGB(int x, int y) { try { WritableRaster r = image.getRaster(); DataBufferInt b = (DataBufferInt)r.getDataBuffer(); int[] d = b.getData(); return d[this.width * (y - this.y) + (x - this.x)]; } catch (Exception e) { return image.getRGB(x - this.x, y - this.y); } } @Override public int[] getRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount) { try { WritableRaster r = image.getRaster(); DataBufferInt b = (DataBufferInt)r.getDataBuffer(); int[] d = b.getData(); if (rgb == null) rgb = new int[offset + rowCount * height]; for (int sy = this.width * (y - this.y) + (x - this.x), dy = offset, iy = 0; iy < height; sy += this.width, dy += rowCount, iy++) { for (int sx = sy, dx = dy, ix = 0; ix < width; sx++, dx++, ix++) { rgb[dx] = d[sx]; } } return rgb; } catch (Exception e) { return image.getRGB(x - this.x, y - this.y, width, height, rgb, offset, rowCount); } } private static class SetRGBAtom implements Atom { private Tile t; private int x, y, width, height; private int[] oldRGB; private int[] newRGB; private int offset, rowCount; public SetRGBAtom(Tile t, int x, int y, int width, int height, int[] rgb, int offset, int rowCount, boolean copy) { this.t = t; this.x = x - t.x; this.y = y - t.y; this.width = width; this.height = height; this.oldRGB = new int[width * height]; t.getRGB(x, y, width, height, this.oldRGB, 0, width); if (copy) { this.newRGB = new int[width * height]; for (int sy = offset, dy = 0, iy = 0; iy < height; sy += rowCount, dy += width, iy++) { for (int sx = sy, dx = dy, ix = 0; ix < width; sx++, dx++, ix++) { this.newRGB[dx] = rgb[sx]; } } this.offset = 0; this.rowCount = width; } else { this.newRGB = rgb; this.offset = offset; this.rowCount = rowCount; } } @Override public boolean canBuildUpon(Atom prev) { return (prev instanceof SetRGBAtom) && (((SetRGBAtom)prev).t == this.t) && (((SetRGBAtom)prev).x == this.x) && (((SetRGBAtom)prev).y == this.y) && (((SetRGBAtom)prev).width == this.width) && (((SetRGBAtom)prev).height == this.height); } @Override public Atom buildUpon(Atom prev) { this.oldRGB = ((SetRGBAtom)prev).oldRGB; return this; } @Override public void redo() { t.image.setRGB(x, y, width, height, newRGB, offset, rowCount); t.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } @Override public void undo() { t.image.setRGB(x, y, width, height, oldRGB, 0, width); t.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } } @Override public void setRGB(int x, int y, int rgb) { if (history != null) history.add(new SetRGBAtom(this, x, y, 1, 1, new int[]{rgb}, 0, 1, false)); this.image.setRGB(x - this.x, y - this.y, rgb); this.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } @Override public void setRGB(int x, int y, int rgb, Shape clip) { if (clip == null || clip.contains(x, y)) { this.setRGB(x, y, rgb); } } @Override public void setRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount) { if (history != null) history.add(new SetRGBAtom(this, x, y, width, height, rgb, offset, rowCount, true)); this.image.setRGB(x - this.x, y - this.y, width, height, rgb, offset, rowCount); this.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } @Override public void setRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount, Shape clip) { if (clip == null || clip.contains(x, y, width, height)) { this.setRGB(x, y, width, height, rgb, offset, rowCount); } else { int[] oldRGB = new int[width * height]; this.getRGB(x, y, width, height, oldRGB, 0, width); for (int oy = 0, ny = offset, ay = y, iy = 0; iy < height; oy += width, ny += rowCount, ay++, iy++) { for (int ox = oy, nx = ny, ax = x, ix = 0; ix < width; ox++, nx++, ax++, ix++) { if (clip.contains(ax, ay)) oldRGB[ox] = rgb[nx]; } } this.setRGB(x, y, width, height, oldRGB, 0, width); } } @Override public void clear(int x, int y, int width, int height) { int[] rgb = new int[width * height]; for (int i = 0; i < rgb.length; i++) rgb[i] = matte; if (history != null) history.add(new SetRGBAtom(this, x, y, width, height, rgb, 0, width, false)); this.image.setRGB(x - this.x, y - this.y, width, height, rgb, 0, width); this.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } @Override public void clear(int x, int y, int width, int height, Shape clip) { if (clip == null || clip.contains(x, y, width, height)) { this.clear(x, y, width, height); } else { int[] oldRGB = new int[width * height]; this.getRGB(x, y, width, height, oldRGB, 0, width); for (int oy = 0, ay = y, iy = 0; iy < height; oy += width, ay++, iy++) { for (int ox = oy, ax = x, ix = 0; ix < width; ox++, ax++, ix++) { if (clip.contains(ax, ay)) oldRGB[ox] = matte; } } this.setRGB(x, y, width, height, oldRGB, 0, width); } } private static class ClearAllAtom implements Atom { private Tile t; private BufferedImage oldImage; private BufferedImage newImage; public ClearAllAtom(Tile t, BufferedImage newImage) { this.t = t; this.oldImage = t.image; this.newImage = newImage; } @Override public boolean canBuildUpon(Atom prev) { return (prev instanceof ClearAllAtom) && (((ClearAllAtom)prev).t == this.t); } @Override public Atom buildUpon(Atom prev) { this.oldImage = ((ClearAllAtom)prev).oldImage; return this; } @Override public void redo() { t.image = newImage; t.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } @Override public void undo() { t.image = oldImage; t.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } } @Override public void clearAll() { BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); int[] rgb = new int[width * height]; for (int i = 0; i < rgb.length; i++) rgb[i] = matte; newImage.setRGB(0, 0, width, height, rgb, 0, width); if (history != null) history.add(new ClearAllAtom(this, newImage)); this.image = newImage; this.notifyTileListeners(TileEvent.TILE_CONTENT_CHANGED); } @Override public Graphics2D createPaintGraphics() { return new TileGraphics( this, this.x, this.y, this.width, this.height, this.image, this.history ); } }