package com.kreative.paint;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.kreative.paint.document.draw.DrawObject;
import com.kreative.paint.document.draw.DrawObjectSurface;
import com.kreative.paint.document.draw.DrawSurface;
import com.kreative.paint.document.draw.ImageDrawObject;
import com.kreative.paint.document.draw.PaintSettings;
import com.kreative.paint.document.tile.PaintSurface;
import com.kreative.paint.document.tile.Tile;
import com.kreative.paint.document.tile.TileSurface;
import com.kreative.paint.document.undo.Atom;
import com.kreative.paint.document.undo.History;
import com.kreative.paint.document.undo.Recordable;
import com.kreative.paint.util.ImageUtils;
public class Layer implements PaintSurface, DrawSurface, Paintable, Recordable {
private History history;
private String name;
private boolean visible;
private boolean locked;
private boolean selected;
private int x;
private int y;
private Shape clip;
private TileSurface ts;
private BufferedImage poppedImage;
private AffineTransform poppedImageTransform;
private DrawObjectSurface ds;
private Graphics2D paintGraphicsCache;
private Graphics2D drawGraphicsCache;
public Layer() {
this(8, 0x00FFFFFF);
}
public Layer(int tileSize, int matte) {
this.name = "";
this.visible = true;
this.locked = false;
this.selected = true;
this.x = 0;
this.y = 0;
this.clip = null;
this.ts = new TileSurface(0, 0, tileSize, tileSize, matte);
this.poppedImage = null;
this.poppedImageTransform = null;
this.ds = new DrawObjectSurface(0, 0);
this.paintGraphicsCache = null;
this.drawGraphicsCache = null;
}
public History getHistory() {
return history;
}
public void setHistory(History history) {
this.history = history;
ts.setHistory(history);
ds.setHistory(history);
}
public String getName() {
return name;
}
public boolean isVisible() {
return visible;
}
public boolean isLocked() {
return locked;
}
public boolean isSelected() {
return selected;
}
public boolean isViewable() {
return visible;
}
public boolean isEditable() {
return visible && selected && !locked;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Point getLocation() {
return new Point(x, y);
}
public Shape getClip() {
return clip;
}
public boolean isImagePopped() {
return poppedImage != null;
}
public BufferedImage getPoppedImage() {
return poppedImage;
}
public AffineTransform getPoppedImageTransform() {
return poppedImageTransform;
}
public int getMatte() {
return ts.getMatte();
}
private static class NameAtom implements Atom {
private Layer l;
private String oldName;
private String newName;
public NameAtom(Layer l, String n) {
this.l = l;
this.oldName = l.name;
this.newName = n;
}
public Atom buildUpon(Atom previousAtom) {
this.oldName = ((NameAtom)previousAtom).oldName;
return this;
}
public boolean canBuildUpon(Atom previousAtom) {
return (previousAtom instanceof NameAtom) && ((NameAtom)previousAtom).l == this.l;
}
public void redo() {
l.name = newName;
}
public void undo() {
l.name = oldName;
}
}
public void setName(String name) {
if (this.name == name) return;
if (history != null) history.add(new NameAtom(this, name));
this.name = name;
}
private static class AttributeAtom implements Atom {
private Layer l;
private boolean oldv, oldl, olds;
private boolean newv, newl, news;
public AttributeAtom(Layer l, boolean vis, boolean lok, boolean sel) {
this.l = l;
this.oldv = l.visible;
this.oldl = l.locked;
this.olds = l.selected;
this.newv = vis;
this.newl = lok;
this.news = sel;
}
public Atom buildUpon(Atom previousAtom) {
this.oldv = ((AttributeAtom)previousAtom).oldv;
this.oldl = ((AttributeAtom)previousAtom).oldl;
this.olds = ((AttributeAtom)previousAtom).olds;
return this;
}
public boolean canBuildUpon(Atom previousAtom) {
return (previousAtom instanceof AttributeAtom) && ((AttributeAtom)previousAtom).l == this.l;
}
public void redo() {
l.visible = newv;
l.locked = newl;
l.selected = news;
}
public void undo() {
l.visible = oldv;
l.locked = oldl;
l.selected = olds;
}
}
public void setVisible(boolean visible) {
if (this.visible == visible) return;
if (history != null) history.add(new AttributeAtom(this, visible, locked, selected));
this.visible = visible;
}
public void setLocked(boolean locked) {
if (this.locked == locked) return;
if (history != null) history.add(new AttributeAtom(this, visible, locked, selected));
this.locked = locked;
}
public void setSelected(boolean selected) {
if (this.selected == selected) return;
if (history != null) history.add(new AttributeAtom(this, visible, locked, selected));
this.selected = selected;
}
private static class LocationAtom implements Atom {
private Layer l;
private int oldX, oldY;
private int newX, newY;
public LocationAtom(Layer l, int x, int y) {
this.l = l;
this.oldX = l.x;
this.oldY = l.y;
this.newX = x;
this.newY = y;
}
public Atom buildUpon(Atom previousAtom) {
this.oldX = ((LocationAtom)previousAtom).oldX;
this.oldY = ((LocationAtom)previousAtom).oldY;
return this;
}
public boolean canBuildUpon(Atom previousAtom) {
return (previousAtom instanceof LocationAtom) && ((LocationAtom)previousAtom).l == this.l;
}
public void redo() {
l.x = newX;
l.y = newY;
}
public void undo() {
l.x = oldX;
l.y = oldY;
}
}
public void setX(int x) {
if (this.x == x) return;
if (history != null) history.add(new LocationAtom(this, x, y));
this.x = x;
}
public void setY(int y) {
if (this.y == y) return;
if (history != null) history.add(new LocationAtom(this, x, y));
this.y = y;
}
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;
}
private static class ClipAtom implements Atom {
private Layer l;
private Shape oldClip;
private Shape newClip;
public ClipAtom(Layer l, Shape clip) {
this.l = l;
this.oldClip = l.clip;
this.newClip = clip;
}
public Atom buildUpon(Atom previousAtom) {
this.oldClip = ((ClipAtom)previousAtom).oldClip;
return this;
}
public boolean canBuildUpon(Atom previousAtom) {
return (previousAtom instanceof ClipAtom) && ((ClipAtom)previousAtom).l == this.l;
}
public void redo() {
l.clip = newClip;
}
public void undo() {
l.clip = oldClip;
}
}
public void setClip(Shape clip) {
if (this.clip == clip) return;
if (history != null) history.add(new ClipAtom(this, clip));
this.clip = clip;
}
private static class PoppedImageAtom implements Atom {
private Layer l;
private BufferedImage oldPoppedImage;
private AffineTransform oldPoppedTx;
private BufferedImage newPoppedImage;
private AffineTransform newPoppedTx;
public PoppedImageAtom(Layer l, BufferedImage pimg, AffineTransform tx) {
this.l = l;
this.oldPoppedImage = l.poppedImage;
this.oldPoppedTx = l.poppedImageTransform;
this.newPoppedImage = pimg;
this.newPoppedTx = tx;
}
public Atom buildUpon(Atom previousAtom) {
this.oldPoppedImage = ((PoppedImageAtom)previousAtom).oldPoppedImage;
this.oldPoppedTx = ((PoppedImageAtom)previousAtom).oldPoppedTx;
return this;
}
public boolean canBuildUpon(Atom previousAtom) {
return (previousAtom instanceof PoppedImageAtom) && ((PoppedImageAtom)previousAtom).l == this.l;
}
public void redo() {
l.poppedImage = newPoppedImage;
l.poppedImageTransform = newPoppedTx;
}
public void undo() {
l.poppedImage = oldPoppedImage;
l.poppedImageTransform = oldPoppedTx;
}
}
public void setPoppedImage(BufferedImage pimg, AffineTransform ptx) {
if (this.poppedImage == pimg && this.poppedImageTransform == ptx) return;
if (history != null) history.add(new PoppedImageAtom(this, pimg, ptx));
this.poppedImage = pimg;
this.poppedImageTransform = ptx;
}
public void popImage(Shape shape, boolean copy) {
if (poppedImage != null) pushImage();
Rectangle bounds = shape.getBounds();
BufferedImage pimg = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
AffineTransform ptx = AffineTransform.getTranslateInstance(bounds.x, bounds.y);
Graphics2D pg = pimg.createGraphics();
pg.translate(-bounds.x, -bounds.y);
pg.setClip(shape);
ts.paint(pg);
pg.dispose();
if (!copy) {
Shape sc = clip;
clip = shape;
Rectangle r = shape.getBounds();
clear(r.x, r.y, r.width, r.height);
clip = sc;
}
setPoppedImage(pimg, ptx);
}
private boolean isTranslation(AffineTransform tx) {
return (tx.getScaleX() == 1.0 && tx.getScaleY() == 1.0 && tx.getShearX() == 0.0 && tx.getShearY() == 0.0);
}
public Image copyImage(Shape shape) {
if (poppedImage != null) {
if (poppedImageTransform == null || isTranslation(poppedImageTransform)) {
return poppedImage;
} else {
Shape bounds = poppedImageTransform.createTransformedShape(new Rectangle(0, 0, poppedImage.getWidth(), poppedImage.getHeight()));
Rectangle boundsBounds = bounds.getBounds();
BufferedImage img = new BufferedImage(boundsBounds.width, boundsBounds.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
AffineTransformOp op = new AffineTransformOp(poppedImageTransform, AffineTransformOp.TYPE_BICUBIC);
g.drawImage(poppedImage, op, -boundsBounds.x, -boundsBounds.y);
g.dispose();
return img;
}
} else {
Rectangle bounds = shape.getBounds();
BufferedImage pimg = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D pg = pimg.createGraphics();
pg.translate(-bounds.x, -bounds.y);
pg.setClip(shape);
ts.paint(pg);
pg.dispose();
return pimg;
}
}
public ImageDrawObject copyImageObject(Shape shape) {
PaintSettings ps = new PaintSettings(null, null);
if (poppedImage != null) {
if (poppedImageTransform == null) {
return ImageDrawObject.forGraphicsDrawImage(ps, (Image)poppedImage, 0, 0);
} else {
return ImageDrawObject.forGraphicsDrawImage(ps, (Image)poppedImage, poppedImageTransform);
}
} else {
Rectangle bounds = shape.getBounds();
BufferedImage pimg = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D pg = pimg.createGraphics();
pg.translate(-bounds.x, -bounds.y);
pg.setClip(shape);
ts.paint(pg);
pg.dispose();
return ImageDrawObject.forGraphicsDrawImage(ps, (Image)pimg, AffineTransform.getTranslateInstance(bounds.x, bounds.y));
}
}
public void pasteImage(Image img, AffineTransform tx) {
if (ImageUtils.prepImage(img)) {
if (poppedImage != null) pushImage();
BufferedImage pimg = ImageUtils.toBufferedImage(img, true);
AffineTransform ptx = AffineTransform.getTranslateInstance(0, 0);
ptx.concatenate(tx);
setPoppedImage(pimg, ptx);
} else {
System.err.println("Error: Failed to paste image. Selection unaffected.");
}
}
public void pushImage() {
if (poppedImage != null) {
AffineTransformOp op = (poppedImageTransform == null) ? null : new AffineTransformOp(poppedImageTransform, AffineTransformOp.TYPE_BICUBIC);
Graphics2D g = getCachedPaintGraphics();
g.setClip(null);
g.setPaint(new Color(ts.getMatte()));
g.setComposite(AlphaComposite.SrcOver);
g.drawImage(poppedImage, op, 0, 0);
setPoppedImage(null, null);
}
}
public void transformPoppedImage(AffineTransform tx) {
if (poppedImage != null) {
AffineTransform ntx = AffineTransform.getTranslateInstance(0, 0);
if (poppedImageTransform != null) ntx.concatenate(poppedImageTransform);
ntx.concatenate(tx);
setPoppedImage(poppedImage, ntx);
}
}
public void deletePoppedImage() {
if (poppedImage != null) {
setPoppedImage(null, null);
} else if (clip != null) {
Rectangle r = clip.getBounds();
clear(r.x, r.y, r.width, r.height);
}
}
public int getTileSize() {
return ts.getTileWidth();
}
public void paint(Graphics2D g) {
ts.paint(g, x, y);
if (poppedImage != null) {
AffineTransform tr = AffineTransform.getTranslateInstance(0, 0);
tr.translate(x, y);
if (poppedImageTransform != null) tr.concatenate(poppedImageTransform);
while (!g.drawImage(poppedImage, tr, null));
}
ds.paint(g, x, y);
}
public void paint(Graphics2D g, int tx, int ty) {
ts.paint(g, x + tx, y + ty);
if (poppedImage != null) {
AffineTransform tr = AffineTransform.getTranslateInstance(0, 0);
tr.translate(x+tx, y+ty);
if (poppedImageTransform != null) tr.concatenate(poppedImageTransform);
while (!g.drawImage(poppedImage, tr, null));
}
ds.paint(g, x + tx, y + ty);
}
public Graphics2D createPaintGraphics() {
Graphics2D g = ts.createPaintGraphics();
g.setClip(clip);
return g;
}
public Graphics2D createDrawGraphics() {
return ds.createDrawGraphics();
}
public Graphics2D getCachedPaintGraphics() {
if (paintGraphicsCache == null)
paintGraphicsCache = ts.createPaintGraphics();
paintGraphicsCache.setClip(clip);
return paintGraphicsCache;
}
public Graphics2D getCachedDrawGraphics() {
if (drawGraphicsCache == null)
drawGraphicsCache = ds.createDrawGraphics();
return drawGraphicsCache;
}
public void flushCache() {
if (paintGraphicsCache != null)
paintGraphicsCache.dispose();
paintGraphicsCache = null;
if (drawGraphicsCache != null)
drawGraphicsCache.dispose();
drawGraphicsCache = null;
}
public int getMinX() {
return Integer.MIN_VALUE;
}
public int getMinY() {
return Integer.MIN_VALUE;
}
public int getMaxX() {
return Integer.MAX_VALUE;
}
public int getMaxY() {
return Integer.MAX_VALUE;
}
public boolean contains(int x, int y) {
return true;
}
public boolean contains(int x, int y, int width, int height) {
return true;
}
public int getRGB(int x, int y) {
return ts.getRGB(x, y);
}
public int[] getRGB(int bx, int by, int width, int height, int[] rgb, int offset, int rowCount) {
return ts.getRGB(bx, by, width, height, rgb, offset, rowCount);
}
public void setRGB(int x, int y, int rgb) {
ts.setRGB(x, y, rgb, clip);
}
public void setRGB(int x, int y, int rgb, Shape clip) {
Area a = new Area(this.clip);
a.intersect(new Area(clip));
ts.setRGB(x, y, rgb, a);
}
public void setRGB(int bx, int by, int width, int height, int[] rgb, int offset, int rowCount) {
ts.setRGB(bx, by, width, height, rgb, offset, rowCount, clip);
}
public void setRGB(int bx, int by, int width, int height, int[] rgb, int offset, int rowCount, Shape clip) {
Area a = new Area(this.clip);
a.intersect(new Area(clip));
ts.setRGB(bx, by, width, height, rgb, offset, rowCount, a);
}
public void clear(int x, int y, int width, int height) {
ts.clear(x, y, width, height, clip);
}
public void clear(int x, int y, int width, int height, Shape clip) {
Area a = new Area(this.clip);
a.intersect(new Area(clip));
ts.clear(x, y, width, height, a);
}
public void clearAll() {
if (clip != null) {
Rectangle r = clip.getBounds();
ts.clear(r.x, r.y, r.width, r.height, clip);
} else {
ts.clearAll();
}
}
public Tile getTile(int x, int y, boolean create) {
return ts.getTile(x, y, create);
}
public void addTile(Tile t) {
ts.addTile(t);
}
public Collection<Tile> getTiles(int bx, int by, int width, int height, boolean create) {
return ts.getTiles(bx, by, width, height, create);
}
public Collection<Tile> getTiles() {
return ts.getTiles();
}
public boolean hasSelection() { return ds.hasSelection(); }
public int getFirstSelectedIndex() { return ds.getFirstSelectedIndex(); }
public int getLastSelectedIndex() { return ds.getLastSelectedIndex(); }
public List<DrawObject> getSelection() { return ds.getSelection(); }
public boolean add(DrawObject o) { return ds.add(o); }
public void add(int index, DrawObject o) { ds.add(index, o); }
public boolean addAll(Collection<? extends DrawObject> c) { return ds.addAll(c); }
public boolean addAll(int index, Collection<? extends DrawObject> c) { return ds.addAll(index, c); }
public void clear() { ds.clear(); }
public boolean contains(Object o) { return ds.contains(o); }
public boolean containsAll(Collection<?> c) { return ds.containsAll(c); }
public DrawObject get(int index) { return ds.get(index); }
public int indexOf(Object o) { return ds.indexOf(o); }
public boolean isEmpty() { return ds.isEmpty(); }
public Iterator<DrawObject> iterator() { return ds.iterator(); }
public int lastIndexOf(Object o) { return ds.lastIndexOf(o); }
public ListIterator<DrawObject> listIterator() { return ds.listIterator(); }
public ListIterator<DrawObject> listIterator(int index) { return ds.listIterator(index); }
public boolean remove(Object o) { return ds.remove(o); }
public DrawObject remove(int index) { return ds.remove(index); }
public boolean removeAll(Collection<?> c) { return ds.removeAll(c); }
public boolean retainAll(Collection<?> c) { return ds.retainAll(c); }
public DrawObject set(int index, DrawObject o) { return ds.set(index, o); }
public int size() { return ds.size(); }
public List<DrawObject> subList(int start, int end) { return ds.subList(start, end); }
public Object[] toArray() { return ds.toArray(); }
public <T> T[] toArray(T[] a) { return ds.toArray(a); }
}