package com.kreative.paint;
import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import com.kreative.paint.material.colorpalette.CheckerboardPaint;
public class CanvasView extends JComponent implements Scrollable {
private static final long serialVersionUID = 1L;
private static final int MAX_BUFFER_SIZE = 4096;
private Canvas theCanvas;
private float scale;
private BufferedImage tmpImg;
private Graphics2D tmpGr;
private List<CanvasPaintListener> listeners;
private Dimension min, pref, max;
public CanvasView(Canvas theCanvas) {
this.theCanvas = theCanvas;
this.scale = 1.0f;
this.tmpImg = new BufferedImage(
Math.min(MAX_BUFFER_SIZE, theCanvas.getWidth()),
Math.min(MAX_BUFFER_SIZE, theCanvas.getHeight()),
BufferedImage.TYPE_INT_ARGB
);
this.tmpGr = tmpImg.createGraphics();
this.listeners = new Vector<CanvasPaintListener>();
this.min = null;
this.pref = null;
this.max = null;
this.setFocusable(true);
this.addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent me) {
CanvasView.this.requestFocusInWindow();
}
public void mousePressed(MouseEvent me) {
CanvasView.this.requestFocusInWindow();
}
});
}
public Canvas getCanvas() {
return theCanvas;
}
public void setCanvas(Canvas theCanvas) {
this.theCanvas = theCanvas;
if (tmpGr != null) tmpGr.dispose();
this.tmpImg = new BufferedImage(
Math.min(MAX_BUFFER_SIZE, (int)Math.ceil(theCanvas.getWidth()*scale)),
Math.min(MAX_BUFFER_SIZE, (int)Math.ceil(theCanvas.getHeight()*scale)),
BufferedImage.TYPE_INT_ARGB
);
this.tmpGr = tmpImg.createGraphics();
this.revalidate();
}
public float getScale() {
return scale;
}
public void setScale(float scale) {
this.scale = scale;
if (tmpGr != null) tmpGr.dispose();
this.tmpImg = new BufferedImage(
Math.min(MAX_BUFFER_SIZE, (int)Math.ceil(theCanvas.getWidth()*scale)),
Math.min(MAX_BUFFER_SIZE, (int)Math.ceil(theCanvas.getHeight()*scale)),
BufferedImage.TYPE_INT_ARGB
);
this.tmpGr = tmpImg.createGraphics();
this.revalidate();
}
protected void finalize() {
if (tmpGr != null) tmpGr.dispose();
this.tmpImg = null;
this.tmpGr = null;
}
public void addPaintListener(CanvasPaintListener pl) {
listeners.add(pl);
}
public void removePaintListener(CanvasPaintListener pl) {
listeners.remove(pl);
}
public CanvasPaintListener[] getPaintListeners() {
return listeners.toArray(new CanvasPaintListener[0]);
}
public Dimension getMinimumSize() {
if (min != null) return min;
else {
Insets i = this.getInsets();
Dimension d = theCanvas.getSize();
return new Dimension((int)(d.width*scale)+i.left+i.right, (int)(d.height*scale)+i.top+i.bottom);
}
}
public Dimension getPreferredSize() {
if (pref != null) return pref;
else {
Insets i = this.getInsets();
Dimension d = theCanvas.getSize();
return new Dimension((int)(d.width*scale)+i.left+i.right, (int)(d.height*scale)+i.top+i.bottom);
}
}
public Dimension getMaximumSize() {
if (max != null) return max;
else {
Insets i = this.getInsets();
Dimension d = theCanvas.getSize();
return new Dimension((int)(d.width*scale)+i.left+i.right, (int)(d.height*scale)+i.top+i.bottom);
}
}
public void setMinimumSize(Dimension d) {
min = d;
}
public void setPreferredSize(Dimension d) {
pref = d;
}
public void setMaximumSize(Dimension d) {
max = d;
}
private boolean painting = false;
public void paintNow() {
if (!painting) {
Graphics g = this.getGraphics();
if (g != null) paintComponent(g);
}
}
protected void paintComponent(Graphics g) {
// see also CanvasController.canvasPainted(Graphics2D g)
painting = true;
int bw = Math.min(MAX_BUFFER_SIZE, (int)Math.ceil(theCanvas.getWidth()*scale));
int bh = Math.min(MAX_BUFFER_SIZE, (int)Math.ceil(theCanvas.getHeight()*scale));
if (tmpImg == null || tmpImg.getWidth() != bw || tmpImg.getHeight() != bh) {
if (tmpGr != null) tmpGr.dispose();
this.tmpImg = new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB);
this.tmpGr = tmpImg.createGraphics();
}
Rectangle vr = (getParent() instanceof JViewport) ? snap16(((JViewport)getParent()).getViewRect()) : new Rectangle(0, 0, bw, bh);
int w = Math.min(vr.width, bw);
int h = Math.min(vr.height, bh);
tmpGr.setComposite(AlphaComposite.SrcOver);
tmpGr.setPaint(CheckerboardPaint.LIGHT);
tmpGr.fillRect(0, 0, w, h);
if (theCanvas != null) {
Shape cl = tmpGr.getClip();
AffineTransform tx = tmpGr.getTransform();
tmpGr.clipRect(0, 0, w, h);
tmpGr.translate(-vr.x, -vr.y);
tmpGr.scale(scale, scale);
theCanvas.paint(tmpGr);
for (CanvasPaintListener pl : listeners) {
pl.canvasPainted(tmpGr);
}
tmpGr.setTransform(tx);
tmpGr.setClip(cl);
}
Insets i = this.getInsets();
int x1 = i.left+vr.x;
int y1 = i.top+vr.y;
int x2 = Math.min(x1+w, this.getWidth()-i.right);
int y2 = Math.min(y1+h, this.getHeight()-i.bottom);
Shape clip = g.getClip();
g.clipRect(x1, y1, x2-x1, y2-y1);
g.drawImage(tmpImg, x1, y1, null);
g.setClip(clip);
painting = false;
}
private Rectangle snap16(Rectangle r) {
int left = r.x & ~0x0F;
int top = r.y & ~0x0F;
int right = r.x+r.width;
if ((right & 0x0F) != 0) { right |= 0x0F; right++; }
int bottom = r.y+r.height;
if ((bottom & 0x0F) != 0) { bottom |= 0x0F; bottom++; }
return new Rectangle(left, top, right-left, bottom-top);
}
public Point2D viewCoordinateToCanvasGraphicsCoordinate(Point p) {
Insets i = this.getInsets();
return new Point2D.Float(((p.x-i.left)/scale), ((p.y-i.top)/scale));
}
public Point2D viewCoordinateToLayerGraphicsCoordinate(Point p, Layer l) {
Insets i = this.getInsets();
Point loc = l.getLocation();
return new Point2D.Float(((p.x-i.left)/scale) - loc.x, ((p.y-i.top)/scale) - loc.y);
}
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return Math.max(1, (int)scale);
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
return visibleRect.width;
case SwingConstants.VERTICAL:
default:
return visibleRect.height;
}
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
public String toString() {
return "com.kreative.paint.CanvasView["+theCanvas+","+scale+"]";
}
}