/* * Copyright © 2009-2011 Rebecca G. Bettencourt / Kreative Software * <p> * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a> * <p> * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * <p> * Alternatively, the contents of this file may be used under the terms * of the GNU Lesser General Public License (the "LGPL License"), in which * case the provisions of LGPL License are applicable instead of those * above. If you wish to allow use of your version of this file only * under the terms of the LGPL License and not to allow others to use * your version of this file under the MPL, indicate your decision by * deleting the provisions above and replace them with the notice and * other provisions required by the LGPL License. If you do not delete * the provisions above, a recipient may use your version of this file * under either the MPL or the LGPL License. * @since PowerPaint 1.0 * @author Rebecca G. Bettencourt, Kreative Software */ package com.kreative.paint; import java.awt.Dimension; import java.awt.Graphics; 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.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Vector; import javax.swing.SwingConstants; 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; import com.kreative.paint.util.ShapeUtils; public class Canvas implements List<Layer>, Printable, Paintable, Recordable { public static final int NORTH = SwingConstants.NORTH; public static final int SOUTH = SwingConstants.SOUTH; public static final int WEST = SwingConstants.WEST; public static final int EAST = SwingConstants.EAST; public static final int NORTH_WEST = SwingConstants.NORTH_WEST; public static final int NORTH_EAST = SwingConstants.NORTH_EAST; public static final int SOUTH_WEST = SwingConstants.SOUTH_WEST; public static final int SOUTH_EAST = SwingConstants.SOUTH_EAST; public static final int CENTER = SwingConstants.CENTER; private History history; private int width; private int height; private int dpiX; private int dpiY; private Area selection; private Vector<Layer> layers; private int hsX; private int hsY; public Canvas(int width, int height) { this(width, height, 72, 72); } public Canvas(int width, int height, int dpi) { this(width, height, dpi, dpi); } public Canvas(int width, int height, int dpiX, int dpiY) { this.width = width; this.height = height; this.dpiX = dpiX; this.dpiY = dpiY; this.layers = new Vector<Layer>(); this.layers.add(new Layer()); this.hsX = 0; this.hsY = 0; } public Canvas(int width, int height, List<Layer> layers) { this(width, height, 72, 72, layers); } public Canvas(int width, int height, int dpi, List<Layer> layers) { this(width, height, dpi, dpi, layers); } public Canvas(int width, int height, int dpiX, int dpiY, List<Layer> layers) { this.width = width; this.height = height; this.dpiX = dpiX; this.dpiY = dpiY; this.layers = new Vector<Layer>(); this.layers.addAll(layers); this.hsX = 0; this.hsY = 0; } public History getHistory() { return history; } public void setHistory(History history) { this.history = history; for (Layer l : layers) l.setHistory(history); } public int getWidth() { return width; } public int getHeight() { return height; } public Dimension getSize() { return new Dimension(width, height); } public int getDPIX() { return dpiX; } public int getDPIY() { return dpiY; } public Dimension getDPI() { return new Dimension(dpiX, dpiY); } public int getHotspotX() { return hsX; } public int getHotspotY() { return hsY; } public Point getHotspot() { return new Point(hsX, hsY); } private static class DimensionAtom implements Atom { private Canvas c; private int oldw, oldh, oldrx, oldry; private int neww, newh, newrx, newry; public DimensionAtom(Canvas c, int width, int height, int x, int y) { this.c = c; this.oldw = c.width; this.oldh = c.height; this.oldrx = c.dpiX; this.oldry = c.dpiY; this.neww = width; this.newh = height; this.newrx = x; this.newry = y; } public Atom buildUpon(Atom previousAtom) { this.oldw = ((DimensionAtom)previousAtom).oldw; this.oldh = ((DimensionAtom)previousAtom).oldh; this.oldrx = ((DimensionAtom)previousAtom).oldrx; this.oldry = ((DimensionAtom)previousAtom).oldry; return this; } public boolean canBuildUpon(Atom previousAtom) { return (previousAtom instanceof DimensionAtom) && ((DimensionAtom)previousAtom).c == this.c; } public void redo() { c.width = neww; c.height = newh; c.dpiX = newrx; c.dpiY = newry; } public void undo() { c.width = oldw; c.height = oldh; c.dpiX = oldrx; c.dpiY = oldry; } } public void setSize(int width, int height) { if (this.width == width && this.height == height) return; if (history != null) history.add(new DimensionAtom(this, width, height, dpiX, dpiY)); this.width = width; this.height = height; } public void setSize(Dimension d) { if (this.width == d.width && this.height == d.height) return; if (history != null) history.add(new DimensionAtom(this, d.width, d.height, dpiX, dpiY)); this.width = d.width; this.height = d.height; } public void setSize(int width, int height, int anchor) { if (history != null) history.add(new DimensionAtom(this, width, height, dpiX, dpiY)); int xmargin = width-this.width; int ymargin = height-this.height; this.width = width; this.height = height; switch (anchor) { case NORTH: case CENTER: case SOUTH: for (Layer l : this) { l.setX(l.getX() + xmargin/2); } break; case NORTH_EAST: case EAST: case SOUTH_EAST: for (Layer l : this) { l.setX(l.getX() + xmargin); } break; } switch (anchor) { case WEST: case CENTER: case EAST: for (Layer l : this) { l.setY(l.getY() + ymargin/2); } break; case SOUTH_WEST: case SOUTH: case SOUTH_EAST: for (Layer l : this) { l.setY(l.getY() + ymargin); } break; } } public void setSize(Dimension d, int anchor) { if (history != null) history.add(new DimensionAtom(this, d.width, d.height, dpiX, dpiY)); int xmargin = d.width-this.width; int ymargin = d.height-this.height; this.width = d.width; this.height = d.height; switch (anchor) { case NORTH: case CENTER: case SOUTH: for (Layer l : this) { l.setX(l.getX() + xmargin/2); } break; case NORTH_EAST: case EAST: case SOUTH_EAST: for (Layer l : this) { l.setX(l.getX() + xmargin); } break; } switch (anchor) { case WEST: case CENTER: case EAST: for (Layer l : this) { l.setY(l.getY() + ymargin/2); } break; case SOUTH_WEST: case SOUTH: case SOUTH_EAST: for (Layer l : this) { l.setY(l.getY() + ymargin); } break; } } public void setDPI(int dpi) { if (this.dpiX == dpi && this.dpiY == dpi) return; if (history != null) history.add(new DimensionAtom(this, width, height, dpi, dpi)); this.dpiX = dpi; this.dpiY = dpi; } public void setDPI(int x, int y) { if (this.dpiX == x && this.dpiY == y) return; if (history != null) history.add(new DimensionAtom(this, width, height, x, y)); this.dpiX = x; this.dpiY = y; } public void setDPI(Dimension d) { if (this.dpiX == d.width && this.dpiY == d.height) return; if (history != null) history.add(new DimensionAtom(this, width, height, d.width, d.height)); this.dpiX = d.width; this.dpiY = d.height; } private static class HotspotAtom implements Atom { private Canvas c; private int oldHSX, oldHSY; private int newHSX, newHSY; public HotspotAtom(Canvas c, int x, int y) { this.c = c; this.oldHSX = c.hsX; this.oldHSY = c.hsY; this.newHSX = x; this.newHSY = y; } public Atom buildUpon(Atom previousAtom) { this.oldHSX = ((HotspotAtom)previousAtom).oldHSX; this.oldHSY = ((HotspotAtom)previousAtom).oldHSY; return this; } public boolean canBuildUpon(Atom previousAtom) { return (previousAtom instanceof HotspotAtom) && ((HotspotAtom)previousAtom).c == this.c; } public void redo() { c.hsX = newHSX; c.hsY = newHSY; } public void undo() { c.hsX = oldHSX; c.hsY = oldHSY; } } public void setHotspotX(int hsX) { if (this.hsX == hsX) return; if (history != null) history.add(new HotspotAtom(this, hsX, hsY)); this.hsX = hsX; } public void setHotspotY(int hsY) { if (this.hsY == hsY) return; if (history != null) history.add(new HotspotAtom(this, hsX, hsY)); this.hsY = hsY; } public void setHotspot(int hsX, int hsY) { if (this.hsX == hsX && this.hsY == hsY) return; if (history != null) history.add(new HotspotAtom(this, hsX, hsY)); this.hsX = hsX; this.hsY = hsY; } public void setHotspot(Point p) { if (this.hsX == p.x && this.hsY == p.y) return; if (history != null) history.add(new HotspotAtom(this, p.x, p.y)); this.hsX = p.x; this.hsY = p.y; } public Layer getPaintDrawLayer() { ListIterator<Layer> i = layers.listIterator(layers.size()); while (i.hasPrevious()) { Layer layer = i.previous(); if (layer.isEditable()) return layer; } return null; } public void paint(Graphics2D g) { for (Layer l : layers) if (l.isViewable()) l.paint(g); } public void paint(Graphics2D g, int tx, int ty) { for (Layer l : layers) if (l.isViewable()) l.paint(g, tx, ty); } public int print(Graphics g, PageFormat pf, int i) throws PrinterException { int nx = (int)Math.ceil(width / pf.getImageableWidth()); int ny = (int)Math.ceil(height / pf.getImageableHeight()); int x = i % nx; int y = i / nx; if (x >= nx || y >= ny) return NO_SUCH_PAGE; else { paint( (Graphics2D)g, (int)(pf.getImageableX() - x*pf.getImageableWidth()), (int)(pf.getImageableY() - y*pf.getImageableHeight()) ); return PAGE_EXISTS; } } public Shape getPaintSelection() { return selection; } private static class PaintSelectionAtom implements Atom { private Canvas c; private Area oldSel; private Area newSel; public PaintSelectionAtom(Canvas c, Area a) { this.c = c; this.oldSel = c.selection; this.newSel = a; } public Atom buildUpon(Atom previousAtom) { this.oldSel = ((PaintSelectionAtom)previousAtom).oldSel; return this; } public boolean canBuildUpon(Atom previousAtom) { return (previousAtom instanceof PaintSelectionAtom) && (((PaintSelectionAtom)previousAtom).c == this.c); } public void redo() { c.selection = newSel; } public void undo() { c.selection = oldSel; } } public void setPaintSelection(Shape s) { Area a = (ShapeUtils.shapeIsEmpty(s) ? null : (new Area(s))); if (selection == a) return; if (history != null) history.add(new PaintSelectionAtom(this, a)); selection = a; } public void addPaintSelection(Shape s) { Area a = (selection == null) ? null : (Area)selection.clone(); Area sa = (ShapeUtils.shapeIsEmpty(s) ? null : (new Area(s))); if (sa != null) { if (a == null) a = sa; else a.add(sa); } if (selection == a) return; if (history != null) history.add(new PaintSelectionAtom(this, a)); selection = a; } public void subtractPaintSelection(Shape s) { Area a = (selection == null) ? null : (Area)selection.clone(); Area sa = (ShapeUtils.shapeIsEmpty(s) ? null : (new Area(s))); if (sa != null && a != null) { a.subtract(sa); } if (selection == a) return; if (history != null) history.add(new PaintSelectionAtom(this, a)); selection = a; } public void xorPaintSelection(Shape s) { Area a = (selection == null) ? null : (Area)selection.clone(); Area sa = (ShapeUtils.shapeIsEmpty(s) ? null : (new Area(s))); if (sa != null) { if (a == null) a = sa; else a.exclusiveOr(sa); } if (selection == a) return; if (history != null) history.add(new PaintSelectionAtom(this, a)); selection = a; } public boolean isPaintSelectionPopped() { for (Layer l : layers) { if (l.isImagePopped()) return true; } return false; } public void updatePaintSelection() { for (Layer l : layers) { if (selection == null) l.setClip(null); else l.setClip(AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(selection)); if (!l.isEditable()) l.pushImage(); } } public void clearPaintSelection() { setPaintSelection(null); for (Layer l : layers) { l.setClip(null); l.pushImage(); } } public void pushPaintSelection() { for (Layer l : layers) { if (selection == null) l.setClip(null); else l.setClip(AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(selection)); l.pushImage(); } } public void popPaintSelection(boolean all, boolean copy) { boolean first = true; ListIterator<Layer> i = layers.listIterator(layers.size()); while (i.hasPrevious()) { Layer l = i.previous(); Shape ls; if (selection == null) ls = null; else ls = AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(selection); l.setClip(ls); if (selection == null) l.pushImage(); else if (!l.isEditable()) l.pushImage(); else if (all) l.popImage(ls, copy); else if (first) { l.popImage(ls, copy); first = false; } else l.pushImage(); } } public void popPaintSelection(Collection<Layer> poppedLayers, boolean copy) { for (Layer l : layers) { Shape ls; if (selection == null) ls = null; else ls = AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(selection); l.setClip(ls); if (selection == null) l.pushImage(); else if (!l.isEditable()) l.pushImage(); else if (poppedLayers.contains(l)) l.popImage(ls, copy); else l.pushImage(); } } public Image copyPaintSelection() { if (selection == null) { return null; } else { Layer l = getPaintDrawLayer(); if (l == null) { return null; } else { return l.copyImage(selection); } } } public void pastePaintSelection(Image img, AffineTransform tx) { if (ImageUtils.prepImage(img)) { int w, h; if (img instanceof BufferedImage) { w = ((BufferedImage)img).getWidth(); h = ((BufferedImage)img).getHeight(); } else { w = img.getWidth(null); h = img.getHeight(null); } Shape s = (tx == null) ? new Rectangle(0, 0, w, h) : tx.createTransformedShape(new Rectangle(0, 0, w, h)); setPaintSelection(s); boolean first = true; ListIterator<Layer> i = layers.listIterator(layers.size()); while (i.hasPrevious()) { Layer l = i.previous(); Shape ls; if (s == null) ls = null; else ls = AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(s); l.setClip(ls); if (s == null) l.pushImage(); else if (!l.isEditable()) l.pushImage(); else if (first) { tx.translate(-l.getX(), -l.getY()); l.pasteImage(img, tx); first = false; } else l.pushImage(); } } else { System.err.println("Error: Failed to paste paint selection. Paint selection unaffected."); } } public void transformPaintSelection(AffineTransform tx) { Shape s = (tx == null || selection == null) ? selection : tx.createTransformedShape(selection); setPaintSelection(s); for (Layer l : layers) { if (s == null) l.setClip(null); else l.setClip(AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(s)); if (l.isImagePopped() && tx != null) { l.transformPoppedImage(tx); } } } public void deletePaintSelection(boolean all) { boolean first = true; ListIterator<Layer> i = layers.listIterator(layers.size()); while (i.hasPrevious()) { Layer l = i.previous(); Shape ls; if (selection == null) ls = null; else ls = AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(selection); l.setClip(ls); if (selection == null) l.pushImage(); else if (!l.isEditable()) l.pushImage(); else if (all) l.deletePoppedImage(); else if (first) { l.deletePoppedImage(); first = false; } else l.pushImage(); } } public void deletePaintSelection(Collection<Layer> poppedLayers) { for (Layer l : layers) { Shape ls; if (selection == null) ls = null; else ls = AffineTransform .getTranslateInstance(-l.getX(), -l.getY()) .createTransformedShape(selection); l.setClip(ls); if (selection == null) l.pushImage(); else if (!l.isEditable()) l.pushImage(); else if (poppedLayers.contains(l)) l.deletePoppedImage(); else l.pushImage(); } } private static class AddLayerAtom implements Atom { private Canvas c; private int index; private Layer l; public AddLayerAtom(Canvas c, Layer l) { this.c = c; this.index = -1; this.l = l; } public AddLayerAtom(Canvas c, int index, Layer l) { this.c = c; this.index = index; this.l = l; } public Atom buildUpon(Atom previousAtom) { return this; } public boolean canBuildUpon(Atom previousAtom) { return false; } public void redo() { if (index < 0) { c.layers.add(l); } else { c.layers.add(index, l); } } public void undo() { if (index < 0) { c.layers.remove(l); } else { if (c.layers.get(index) == l) c.layers.remove(index); else c.layers.remove(l); } } } public boolean add(Layer o) { if (history != null) { history.add(new AddLayerAtom(this, o)); o.setHistory(history); } return layers.add(o); } public void add(int index, Layer element) { if (history != null) { history.add(new AddLayerAtom(this, index, element)); element.setHistory(history); } layers.add(index, element); } private static class ChangeLayersAtom implements Atom { private Canvas c; private Vector<Layer> oldLayers; private Vector<Layer> newLayers; public ChangeLayersAtom(Canvas c, List<Layer> oldl, List<Layer> newl) { this.c = c; this.oldLayers = new Vector<Layer>(); this.oldLayers.addAll(oldl); this.newLayers = new Vector<Layer>(); this.newLayers.addAll(newl); } public Atom buildUpon(Atom previousAtom) { this.oldLayers = ((ChangeLayersAtom)previousAtom).oldLayers; return this; } public boolean canBuildUpon(Atom previousAtom) { return (previousAtom instanceof ChangeLayersAtom) && ((ChangeLayersAtom)previousAtom).c == this.c; } public void redo() { c.layers.clear(); c.layers.addAll(newLayers); } public void undo() { c.layers.clear(); c.layers.addAll(oldLayers); } } private Vector<Layer> layersChangTmp; private void layersChanging() { if (history != null) { layersChangTmp = new Vector<Layer>(); layersChangTmp.addAll(layers); } } private void layersChanged() { if (history != null) { history.add(new ChangeLayersAtom(this, layersChangTmp, this.layers)); for (Layer d : this.layers) d.setHistory(history); layersChangTmp = null; } } public boolean addAll(Collection<? extends Layer> c) { layersChanging(); boolean ret = layers.addAll(c); layersChanged(); return ret; } public boolean addAll(int index, Collection<? extends Layer> c) { layersChanging(); boolean ret = layers.addAll(index, c); layersChanged(); return ret; } public void clear() { layersChanging(); layers.clear(); layersChanged(); } public boolean contains(Object o) { return layers.contains(o); } public boolean containsAll(Collection<?> c) { return layers.containsAll(c); } public Layer get(int index) { return layers.get(index); } public int indexOf(Object o) { return layers.indexOf(o); } public boolean isEmpty() { return layers.isEmpty(); } public Iterator<Layer> iterator() { return layers.iterator(); } public int lastIndexOf(Object o) { return layers.lastIndexOf(o); } public ListIterator<Layer> listIterator() { return layers.listIterator(); } public ListIterator<Layer> listIterator(int index) { return layers.listIterator(index); } private static class RemoveLayerAtom implements Atom { private Canvas c; private int index; private Object o; public RemoveLayerAtom(Canvas c, int index, Object o) { this.c = c; this.index = index; this.o = o; } public Atom buildUpon(Atom previousAtom) { return this; } public boolean canBuildUpon(Atom previousAtom) { return false; } public void redo() { if (c.layers.get(index) == o) c.layers.remove(index); else c.layers.remove(o); } public void undo() { c.layers.add(index, (Layer)o); } } public boolean remove(Object o) { if (history != null) history.add(new RemoveLayerAtom(this, layers.indexOf(o), o)); return layers.remove(o); } public Layer remove(int index) { if (history != null) history.add(new RemoveLayerAtom(this, index, layers.get(index))); return layers.remove(index); } public boolean removeAll(Collection<?> c) { layersChanging(); boolean ret = layers.removeAll(c); layersChanged(); return ret; } public boolean retainAll(Collection<?> c) { layersChanging(); boolean ret = layers.retainAll(c); layersChanged(); return ret; } private static class SetLayerAtom implements Atom { private Canvas c; private int index; private Layer oldl; private Layer newl; public SetLayerAtom(Canvas c, int index, Layer l) { this.c = c; this.index = index; this.oldl = c.layers.get(index); this.newl = l; } public Atom buildUpon(Atom previousAtom) { this.oldl = ((SetLayerAtom)previousAtom).oldl; return this; } public boolean canBuildUpon(Atom previousAtom) { return (previousAtom instanceof SetLayerAtom) && (((SetLayerAtom)previousAtom).c == this.c) && (((SetLayerAtom)previousAtom).index == this.index); } public void redo() { c.layers.set(index, newl); } public void undo() { c.layers.set(index, oldl); } } public Layer set(int index, Layer element) { if (history != null) history.add(new SetLayerAtom(this, index, element)); return layers.set(index, element); } public int size() { return layers.size(); } public List<Layer> subList(int fromIndex, int toIndex) { return layers.subList(fromIndex, toIndex); } public Object[] toArray() { return layers.toArray(); } public <T> T[] toArray(T[] a) { return layers.toArray(a); } public String toString() { return "com.kreative.paint.Canvas["+width+","+height+","+dpiX+","+dpiY+","+layers+"]"; } }