package com.kreative.paint.document.layer; import java.awt.Graphics2D; 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 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.tile.TileSurfaceEvent; import com.kreative.paint.document.tile.TileSurfaceListener; import com.kreative.paint.document.undo.Atom; import com.kreative.paint.document.undo.History; public class PaintLayer extends Layer implements PaintSurface { private TileSurface ts; private Shape clip; private BufferedImage poppedImage; private AffineTransform poppedImageTransform; private TileSurfaceListener listener; public PaintLayer(String name, int matte) { this(name, 8, 8, matte); } public PaintLayer(String name, int tileWidth, int tileHeight, int matte) { super(name); this.ts = new TileSurface(0, 0, tileWidth, tileHeight, matte); this.clip = null; this.poppedImage = null; this.poppedImageTransform = null; this.listener = new PaintLayerTileSurfaceListener(this); this.ts.addTileSurfaceListener(this.listener); } private PaintLayer(PaintLayer o) { super(o); this.ts = o.ts.clone(); this.clip = o.clip; this.poppedImage = cloneImage(o.poppedImage); this.poppedImageTransform = o.poppedImageTransform; this.listener = new PaintLayerTileSurfaceListener(this); this.ts.addTileSurfaceListener(this.listener); } @Override public void setHistory(History history) { super.setHistory(history); this.ts.setHistory(history); } @Override public PaintLayer clone() { return new PaintLayer(this); } @Override protected void paintImpl(Graphics2D g, int gx, int gy, int gw, int gh) { ts.paint(g); if (poppedImage != null) { while (!g.drawImage( poppedImage, poppedImageTransform, null )); } } public Shape getClip() { return clip; } private static class ClipAtom implements Atom { private PaintLayer l; private Shape oldClip; private Shape newClip; public ClipAtom(PaintLayer l, Shape newClip) { this.l = l; this.oldClip = l.clip; this.newClip = newClip; } @Override public boolean canBuildUpon(Atom prev) { return (prev instanceof ClipAtom) && (((ClipAtom)prev).l == this.l); } @Override public Atom buildUpon(Atom prev) { this.oldClip = ((ClipAtom)prev).oldClip; return this; } @Override public void undo() { l.clip = oldClip; l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } @Override public void redo() { l.clip = newClip; l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } } public void setClip(Shape clip) { if (this.clip == clip) return; if (history != null) history.add(new ClipAtom(this, clip)); this.clip = clip; this.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } public boolean isImagePopped() { return poppedImage != null; } public BufferedImage getPoppedImage() { return poppedImage; } public AffineTransform getPoppedImageTransform() { return poppedImageTransform; } private static class PoppedImageAtom implements Atom { private PaintLayer l; private BufferedImage oldImage; private AffineTransform oldTx; private BufferedImage newImage; private AffineTransform newTx; public PoppedImageAtom(PaintLayer l, BufferedImage image, AffineTransform tx) { this.l = l; this.oldImage = l.poppedImage; this.oldTx = l.poppedImageTransform; this.newImage = image; this.newTx = tx; } @Override public boolean canBuildUpon(Atom prev) { return (prev instanceof PoppedImageAtom) && (((PoppedImageAtom)prev).l == this.l); } @Override public Atom buildUpon(Atom prev) { this.oldImage = ((PoppedImageAtom)prev).oldImage; this.oldTx = ((PoppedImageAtom)prev).oldTx; return this; } @Override public void undo() { l.poppedImage = oldImage; l.poppedImageTransform = oldTx; l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } @Override public void redo() { l.poppedImage = newImage; l.poppedImageTransform = newTx; l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } } public void setPoppedImage(BufferedImage image, AffineTransform tx) { if (this.poppedImage == image && this.poppedImageTransform == tx) return; if (history != null) history.add(new PoppedImageAtom(this, image, tx)); this.poppedImage = image; this.poppedImageTransform = tx; this.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } public BufferedImage copyImage(boolean aa) { if (poppedImage != null) { if (isTranslation(poppedImageTransform)) { return cloneImage(poppedImage); } else { Rectangle bounds = new Rectangle(0, 0, poppedImage.getWidth(), poppedImage.getHeight()); bounds = poppedImageTransform.createTransformedShape(bounds).getBounds(); BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); g.drawImage( poppedImage, new AffineTransformOp( poppedImageTransform, aa ? AffineTransformOp.TYPE_BICUBIC : AffineTransformOp.TYPE_NEAREST_NEIGHBOR ), -bounds.x, -bounds.y ); g.dispose(); return image; } } else { Rectangle bounds = clip.getBounds(); BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); g.translate(-bounds.x, -bounds.y); g.setClip(clip); ts.paint(g); g.dispose(); return image; } } public ImageDrawObject copyImageObject() { PaintSettings ps = new PaintSettings(null, null); if (poppedImage != null) { if (poppedImageTransform == null) { return ImageDrawObject.forGraphicsDrawImage(ps, poppedImage, 0, 0); } else { return ImageDrawObject.forGraphicsDrawImage(ps, poppedImage, poppedImageTransform); } } else { Rectangle bounds = clip.getBounds(); BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); g.translate(-bounds.x, -bounds.y); g.setClip(clip); ts.paint(g); g.dispose(); return ImageDrawObject.forGraphicsDrawImage(ps, image, bounds.x, bounds.y); } } public void pasteImage(BufferedImage image, AffineTransform tx, boolean aa) { if (poppedImage != null) pushImage(aa); setPoppedImage(cloneImage(image), tx); } public void popImage(boolean copy, boolean aa) { if (poppedImage != null) pushImage(aa); Rectangle bounds = clip.getBounds(); BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); g.translate(-bounds.x, -bounds.y); g.setClip(clip); ts.paint(g); g.dispose(); if (!copy) ts.clear(bounds.x, bounds.y, bounds.width, bounds.height, clip); setPoppedImage(image, AffineTransform.getTranslateInstance(bounds.x, bounds.y)); } public void pushImage(boolean aa) { if (poppedImage != null) { Graphics2D g = ts.createPaintGraphics(); g.drawImage( poppedImage, (poppedImageTransform == null) ? null : new AffineTransformOp( poppedImageTransform, aa ? AffineTransformOp.TYPE_BICUBIC : AffineTransformOp.TYPE_NEAREST_NEIGHBOR ), 0, 0 ); g.dispose(); setPoppedImage(null, null); } } public void transformPoppedImage(AffineTransform tx) { if (poppedImage != null) { AffineTransform newTx = new AffineTransform(); if (tx != null) newTx.concatenate(tx); if (poppedImageTransform != null) newTx.concatenate(poppedImageTransform); setPoppedImage(poppedImage, newTx); } } public void deletePoppedImage() { if (poppedImage != null) { setPoppedImage(null, null); } else if (clip != null) { Rectangle r = clip.getBounds(); ts.clear(r.x, r.y, r.width, r.height, clip); } } public int getTileWidth() { return ts.getTileWidth(); } public int getTileHeight() { return ts.getTileHeight(); } public int getMatte() { return ts.getMatte(); } public void addTile(Tile t) { ts.addTile(t); } public Tile getTile(int x, int y, boolean create) { return ts.getTile(x, y, create); } public Collection<Tile> getTiles(int x, int y, int width, int height, boolean create) { return ts.getTiles(x, y, width, height, create); } public Collection<Tile> getTiles() { return ts.getTiles(); } @Override public int getMinX() { return ts.getMinX(); } @Override public int getMinY() { return ts.getMinY(); } @Override public int getMaxX() { return ts.getMaxX(); } @Override public int getMaxY() { return ts.getMaxY(); } @Override public boolean contains(int x, int y) { return ts.contains(x, y); } @Override public boolean contains(int x, int y, int width, int height) { return ts.contains(x, y, width, height); } @Override public int getRGB(int x, int y) { return ts.getRGB(x, y); } @Override public int[] getRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount) { return ts.getRGB(x, y, width, height, rgb, offset, rowCount); } @Override public void setRGB(int x, int y, int rgb) { ts.setRGB(x, y, rgb, clip); } @Override public void setRGB(int x, int y, int rgb, Shape clip) { ts.setRGB(x, y, rgb, intersect(this.clip, clip)); } @Override public void setRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount) { ts.setRGB(x, y, width, height, rgb, offset, rowCount); } @Override public void setRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount, Shape clip) { ts.setRGB(x, y, width, height, rgb, offset, rowCount, intersect(this.clip, clip)); } @Override public void clear(int x, int y, int width, int height) { ts.clear(x, y, width, height, clip); } @Override public void clear(int x, int y, int width, int height, Shape clip) { ts.clear(x, y, width, height, intersect(this.clip, clip)); } @Override public void clearAll() { if (clip != null) { Rectangle r = clip.getBounds(); ts.clear(r.x, r.y, r.width, r.height, clip); } else { ts.clearAll(); } } @Override public Graphics2D createPaintGraphics() { Graphics2D g = ts.createPaintGraphics(); g.setClip(clip); return g; } private static BufferedImage cloneImage(BufferedImage src) { if (src == null) return null; int width = src.getWidth(); int height = src.getHeight(); int[] rgb = new int[width * height]; BufferedImage dst = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); src.getRGB(0, 0, width, height, rgb, 0, width); dst.setRGB(0, 0, width, height, rgb, 0, width); return dst; } private static boolean isTranslation(AffineTransform tx) { return (tx == null) || ( tx.getScaleX() == 1 && tx.getScaleY() == 1 && tx.getShearX() == 0 && tx.getShearY() == 0 ); } private static Shape intersect(Shape a, Shape b) { if (a == null) return b; if (b == null) return a; Area c = new Area(a); c.intersect(new Area(b)); return c; } private static class PaintLayerTileSurfaceListener implements TileSurfaceListener { private final PaintLayer l; public PaintLayerTileSurfaceListener(PaintLayer l) { this.l = l; } @Override public void tileSurfaceLocationChanged(TileSurfaceEvent e) { l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } @Override public void tileSurfaceMatteChanged(TileSurfaceEvent e) { l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } @Override public void tileSurfaceContentChanged(TileSurfaceEvent e) { l.notifyLayerListeners(LayerEvent.LAYER_CONTENT_CHANGED); } } }