/**
* @file ImageAction.java
* @brief Collection of various repeatable image edit actions that can be undone.
*
* @section License
*
* Copyright (C) 2008, 2012 IsmAvatar <IsmAvatar@gmail.com>
* Copyright (C) 2013 jimn346 <jds9496@gmail.com>
*
* This file is a part of JEIE.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
package org.jeie;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import org.jeie.Algorithm.EdgeDetect;
import org.jeie.Algorithm.FloodFill;
import org.jeie.Canvas.RenderMode;
import org.jeie.OptionComponent.FillOptions.FillType;
public interface ImageAction
{
public boolean copiesRaster();
public void paint(Graphics g);
/**
* A Heavy-weight Image Action is an Image Action that depends on
* the raster produced immediately prior to the action. As a result,
* if any prior actions are altered, heavy-weight actions will need
* to recalculate based on the new prior raster.
* <p>
* Generally, a user should be warned about this fact, as it may yield
* undesirable results, and should potentially be given the option to
* destroy or even manually update later heavy-weight image actions.
*/
public static interface HeavyImageAction extends ImageAction
{
public void recalculate(BufferedImage source);
}
public static class Resize implements ImageAction
{
public int w, h;
public void paint(Graphics g)
{
g.setClip(0,0,w,h);
g.clipRect(0,0,w,h);
}
public boolean copiesRaster()
{
return false;
}
}
public static class TextAction implements ImageAction
{
public Color color;
public Font font;
public Point p;
public Canvas canvas;
public String text;
public OptionComponent.TextOptions.Alignment halign, valign;
public TextAction(Canvas canvas, Point p, Color col, Font f, String t, OptionComponent.TextOptions.Alignment h, OptionComponent.TextOptions.Alignment v)
{
this.canvas = canvas;
this.p = p;
color = col;
font = f;
text = t;
halign = h;
valign = v;
}
public void paint(Graphics g)
{
int x = (int) p.getX();
int y = (int) p.getY();
FontMetrics metrics = g.getFontMetrics(font);
int w = metrics.stringWidth(text);
int h = metrics.getHeight();
if (halign == OptionComponent.TextOptions.Alignment.CENTER)
x -= w / 2;
else if (halign == OptionComponent.TextOptions.Alignment.RIGHT)
x -= w;
if (valign == OptionComponent.TextOptions.Alignment.MIDDLE)
y += h / 2;
else if (valign == OptionComponent.TextOptions.Alignment.TOP)
y += h;
g.setFont(font);
g.setColor(color);
if (canvas.renderMode != RenderMode.TILED)
g.drawString(text,x,y);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; x + w - dx >= 0; dx += img.width)
for (int dy = 0; y + h - dy >= 0; dy += img.height)
g.drawString(text,x - dx,y - dy);
}
}
public boolean copiesRaster()
{
return false;
}
}
public static class RectangleAction implements ImageAction
{
public Color out, in;
public Point p1, p2;
public Canvas canvas;
public RectangleAction(Canvas canvas, Point p, Color out, Color in)
{
this.canvas = canvas;
this.out = out;
this.in = in;
p1 = p;
p2 = p;
}
public void paint(Graphics g)
{
Rectangle r = new Rectangle(p1);
r.add(p2);
if (out != null)
{
g.setColor(out);
if (canvas.renderMode != RenderMode.TILED)
g.drawRect(r.x,r.y,r.width,r.height);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; r.x + r.width - dx >= 0; dx += img.width)
for (int dy = 0; r.y + r.height - dy >= 0; dy += img.height)
g.drawRect(r.x - dx,r.y - dy,r.width,r.height);
}
}
if (in != null)
{
g.setColor(in);
if (canvas.renderMode != RenderMode.TILED)
g.fillRect(r.x + 1,r.y + 1,r.width - 1,r.height - 1);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; r.x + r.width - dx >= 0; dx += img.width)
for (int dy = 0; r.y + r.height - dy >= 0; dy += img.height)
g.fillRect(r.x - dx + 1,r.y - dy + 1,r.width - 1,r.height - 1);
}
}
}
public boolean copiesRaster()
{
return false;
}
}
public static class RoundRectangleAction implements ImageAction
{
public Color out, in;
public Point p1, p2;
public Canvas canvas;
public RoundRectangleAction(Canvas canvas, Point p, Color out, Color in)
{
this.canvas = canvas;
this.out = out;
this.in = in;
p1 = p;
p2 = p;
}
public void paint(Graphics g)
{
Rectangle r = new Rectangle(p1);
r.add(p2);
if (in != null)
{
g.setColor(in);
if (canvas.renderMode != RenderMode.TILED)
g.fillRoundRect(r.x,r.y,r.width,r.height,16,16);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; r.x + r.width - dx >= 0; dx += img.width)
for (int dy = 0; r.y + r.height - dy >= 0; dy += img.height)
g.fillRoundRect(r.x - dx,r.y - dy,r.width,r.height,16,16);
}
}
if (out != null)
{
g.setColor(out);
if (canvas.renderMode != RenderMode.TILED)
g.drawRoundRect(r.x,r.y,r.width,r.height,16,16);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; r.x + r.width - dx >= 0; dx += img.width)
for (int dy = 0; r.y + r.height - dy >= 0; dy += img.height)
g.drawRoundRect(r.x - dx,r.y - dy,r.width,r.height,16,16);
}
}
}
public boolean copiesRaster()
{
return false;
}
}
public static class OvalAction implements ImageAction
{
public Color out, in;
public Point p1, p2;
public Canvas canvas;
public OvalAction(Canvas canvas, Point p, Color out, Color in)
{
this.canvas = canvas;
this.out = out;
this.in = in;
p1 = p;
p2 = p;
}
public void paint(Graphics g)
{
Rectangle r = new Rectangle(p1);
r.add(p2);
if (in != null)
{
g.setColor(in);
if (canvas.renderMode != RenderMode.TILED)
g.fillOval(r.x,r.y,r.width,r.height);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; r.x + r.width - dx >= 0; dx += img.width)
for (int dy = 0; r.y + r.height - dy >= 0; dy += img.height)
g.fillOval(r.x - dx,r.y - dy,r.width,r.height);
}
}
if (out != null)
{
g.setColor(out);
if (canvas.renderMode != RenderMode.TILED)
g.drawOval(r.x,r.y,r.width,r.height);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; r.x + r.width - dx >= 0; dx += img.width)
for (int dy = 0; r.y + r.height - dy >= 0; dy += img.height)
g.drawOval(r.x - dx,r.y - dy,r.width,r.height);
}
}
}
public boolean copiesRaster()
{
return false;
}
}
public static class LineAction implements ImageAction
{
public Color c;
public Point p1, p2;
public int diameter;
Canvas canvas;
public LineAction(Canvas canvas, Point p, Color c, int diam)
{
this.c = c;
this.canvas = canvas;
p1 = p;
p2 = p;
diameter = diam;
}
public void paint(Graphics g)
{
g.setColor(c);
Graphics2D g2d = (Graphics2D) g;
Stroke s = g2d.getStroke();
g2d.setStroke(new BasicStroke(diameter));//diameter>2?diameter:diameter/2));
if (canvas.renderMode != RenderMode.TILED)
g2d.drawLine(p1.x,p1.y,p2.x,p2.y);
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; p2.x - dx >= 0 || p1.x - dx >= 0; dx += img.width)
for (int dy = 0; p2.y - dy >= 0 || p1.y - dy >= 0; dy += img.height)
g2d.drawLine(p1.x - dx,p1.y - dy,p2.x - dx,p2.y - dy);
}
g2d.setStroke(s);
}
public boolean copiesRaster()
{
return false;
}
}
public static class GradientAction implements ImageAction
{
public Color c1, c2;
public Point p1, p2;
public OptionComponent.GradientOptions.GradientType type;
Canvas canvas;
public GradientAction(Canvas canvas, Point p, Color c1, Color c2, OptionComponent.GradientOptions.GradientType type)
{
this.c1 = c1;
this.c2 = c2;
this.canvas = canvas;
p1 = p;
p2 = p;
this.type = type;
}
public void paint(Graphics g)
{
Paint paint = null;
if (p1.equals(p2))
paint = c2;
else
switch(type)
{
case LINEAR:
paint = new LinearGradientPaint(p1, p2, new float[] { 0f, 1f }, new Color[] { c1, c2 });
break;
case MIRRORED:
int xDist = (p2.x - p1.x);
int yDist = (p2.y - p1.y);
Point newPoint = new Point(p1.x - xDist, p1.y - yDist);
paint = new LinearGradientPaint(newPoint, p2, new float[] { 0f, .5f, 1f }, new Color[] { c2, c1, c2 });
break;
case RADIAL:
paint = new RadialGradientPaint(p1, (float) p1.distance(p2), new float[] { 0f, 1f }, new Color[] { c1, c2 });
break;
case CONICAL:
paint = c2; //TODO: implement this.
break;
case SQUARE:
paint = c2; //TODO: implement this.
break;
}
Graphics2D g2d = (Graphics2D) g;
Paint oldPaint = g2d.getPaint();
g2d.setPaint(paint);
g2d.fillRect(0,0,canvas.getWidth(),canvas.getHeight());
g2d.setPaint(oldPaint);
}
public boolean copiesRaster()
{
return false;
}
}
public static class PointAction implements ImageAction
{
static final int MAX_FREE_POINTS = 64;
public Canvas canvas;
public Color c;
LinkedList<Point> pts = new LinkedList<Point>();
BufferedImage cache;
int cacheX, cacheY;
int minX = Integer.MAX_VALUE, minY = minX, maxX = 0, maxY = 0;
public PointAction(Canvas canvas, Color c)
{
this.canvas = canvas;
this.c = c;
}
protected void toCache()
{
int newMinX = minX;
int newMinY = minY;
int w = maxX - minX + 1;
int h = maxY - minY + 1;
if (cache != null)
{
newMinX = Math.min(minX,cacheX);
newMinY = Math.min(minY,cacheY);
int newMaxX = Math.max(maxX,cache.getWidth() + cacheX);
int newMaxY = Math.max(maxY,cache.getHeight() + cacheY);
w = newMaxX - newMinX + 1;
h = newMaxY - newMinY + 1;
}
BufferedImage newCache = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
Graphics g = newCache.createGraphics();
paint(g,-newMinX,-newMinY);
//reset our vars
cache = newCache;
cacheX = newMinX;
cacheY = newMinY;
Point p = pts.getLast();
pts.clear();
pts.add(p);
minX = p.x;
minY = p.y;
maxX = p.x;
maxY = p.y;
}
public void add(Point p)
{
if (pts.add(p))
{
if (p.x < minX) minX = p.x;
else if (p.x > canvas.imageWidth()) minX = Math.min(minX,0);
if (p.y < minY) minY = p.y;
else if (p.y > canvas.imageHeight()) minY = Math.min(minY,0);
if (p.x > maxX) maxX = p.x;
if (p.y > maxY) maxY = p.y;
if (pts.size() > MAX_FREE_POINTS) toCache();
}
}
public void paint(Graphics g)
{
paint(g,0,0);
}
public void paint(Graphics g, int shiftX, int shiftY)
{
if (cache != null) g.drawImage(cache,cacheX + shiftX,cacheY + shiftY,null);
g.setColor(c);
Point prevPoint = pts.getFirst();
if (canvas.renderMode != RenderMode.TILED)
for (Point p : pts)
{
g.drawLine(prevPoint.x + shiftX,prevPoint.y + shiftY,p.x + shiftX,p.y + shiftY);
prevPoint = p;
}
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; dx <= maxX + shiftX; dx += img.width)
for (int dy = 0; dy <= maxY + shiftY; dy += img.height)
{
for (Point p : pts)
{
g.drawLine(prevPoint.x + shiftX - dx,prevPoint.y + shiftY - dy,p.x + shiftX - dx,p.y
+ shiftY - dy);
prevPoint = p;
}
prevPoint = pts.getFirst();
}
}
}
public boolean copiesRaster()
{
return false;
}
}
public static class PaintbrushAction implements ImageAction
{
static final int MAX_FREE_POINTS = 64;
public Canvas canvas;
public Color c;
LinkedList<Point> pts = new LinkedList<Point>();
BufferedImage cache;
int cacheX, cacheY;
int diameter;
int minX = Integer.MAX_VALUE, minY = minX, maxX = 0, maxY = 0;
public PaintbrushAction(Canvas canvas, Color c, int diam)
{
this.canvas = canvas;
this.c = c;
this.diameter = diam;
}
protected void toCache()
{
int newMinX = minX;
int newMinY = minY;
int w = maxX - minX + 1;
int h = maxY - minY + 1;
if (cache != null)
{
newMinX = Math.min(minX,cacheX);
newMinY = Math.min(minY,cacheY);
int newMaxX = Math.max(maxX,cache.getWidth() + cacheX);
int newMaxY = Math.max(maxY,cache.getHeight() + cacheY);
w = newMaxX - newMinX + 1;
h = newMaxY - newMinY + 1;
}
BufferedImage newCache = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
Graphics g = newCache.createGraphics();
paint(g,-newMinX,-newMinY);
//reset our vars
cache = newCache;
cacheX = newMinX;
cacheY = newMinY;
Point p = pts.getLast();
pts.clear();
pts.add(p);
minX = p.x - diameter;
minY = p.y - diameter;
maxX = p.x + diameter;
maxY = p.y + diameter;
}
public void add(Point p)
{
if (pts.add(p))
{
if (p.x < minX) minX = p.x - diameter;
else if (p.x > canvas.imageWidth()) minX = Math.min(minX,0);
if (p.y < minY) minY = p.y - diameter;
else if (p.y > canvas.imageHeight()) minY = Math.min(minY,0);
if (p.x > maxX) maxX = p.x + diameter;
if (p.y > maxY) maxY = p.y + diameter;
if (pts.size() > MAX_FREE_POINTS) toCache();
}
}
public void paint(Graphics g)
{
paint(g,0,0);
}
public void paint(Graphics g, int shiftX, int shiftY)
{
if (cache != null) g.drawImage(cache,cacheX + shiftX,cacheY + shiftY,null);
g.setColor(c);
Point prevPoint = pts.getFirst();
Graphics2D g2d = (Graphics2D) g;
Stroke s = g2d.getStroke();
//Composite cmp = g2d.getComposite();
g2d.setStroke(new BasicStroke(diameter, // Line width
BasicStroke.CAP_ROUND, // End-cap style
BasicStroke.JOIN_ROUND));
// for eraser
//g2d.setComposite(AlphaComposite.SrcIn);
if (canvas.renderMode != RenderMode.TILED)
for (Point p : pts)
{
g2d.drawLine(prevPoint.x + shiftX,prevPoint.y + shiftY,p.x + shiftX,p.y + shiftY);
prevPoint = p;
}
else
{
Dimension img = canvas.getImageSize();
for (int dx = 0; dx <= maxX + shiftX; dx += img.width)
for (int dy = 0; dy <= maxY + shiftY; dy += img.height)
{
for (Point p : pts)
{
g2d.drawLine(prevPoint.x + shiftX - dx,prevPoint.y + shiftY - dy,p.x + shiftX - dx,p.y
+ shiftY - dy);
prevPoint = p;
}
prevPoint = pts.getFirst();
}
}
//g2d.setComposite(cmp);
g2d.setStroke(s);
}
public boolean copiesRaster()
{
return false;
}
}
public static class FillAction implements HeavyImageAction
{
Point origin;
int threshold;
Color c1, c2;
Canvas canvas;
BufferedImage source, myCache;
FloodFill floodFill;
EdgeDetect floodEdge = null;
public FillAction(Canvas canv, BufferedImage source, Point origin, Color c1, Color c2,
int threshold, FillType fillType)
{
this.source = source;
this.origin = origin;
this.c1 = c1;
this.c2 = c2;
this.threshold = threshold;
this.canvas = canv;
}
protected BufferedImage getCache()
{
if (floodFill != null) return myCache;
floodFill = new FloodFill(canvas,source,origin,threshold);
floodEdge = null;
int w = floodFill.maxX - floodFill.minX + 1;
int h = floodFill.maxY - floodFill.minY + 1;
int rgb = c1.getRGB();
myCache = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
if (c1.equals(c2))
{
for (Point p : floodFill.set)
myCache.setRGB(p.x - floodFill.minX,p.y - floodFill.minY,rgb);
return myCache;
}
floodEdge = new EdgeDetect(floodFill,canvas);
if (c2 != null)
{
int rgb2 = c2.getRGB();
for (Point p : floodFill.set)
myCache.setRGB(p.x - floodFill.minX,p.y - floodFill.minY,rgb2);
}
for (Point p : floodEdge.set)
myCache.setRGB(p.x - floodFill.minX,p.y - floodFill.minY,rgb);
return myCache;
}
public void recalculate(BufferedImage source)
{
floodFill = null;
this.source = source;
}
public void paint(Graphics g)
{
g.drawImage(getCache(),floodFill.minX,floodFill.minY,null);
}
public boolean copiesRaster()
{
return false;
}
}
}