/**
* @file Algorithm.java
* @brief Effect utility class.
*
* @section License
*
* Copyright (C) 2014 JoshDreamland
*
* 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.Dimension;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import org.jeie.Canvas.RenderMode;
public class Algorithm
{
/**
* A class to calculate a set of points which match (in color comparison
* and spatial contiguity) a given point on a canvas. Useful for flood-fill
* operations and magic wand selection.
* @author IsmAvatar
*/
public static class FloodFill
{
//input
BufferedImage source;
int targetRGB;
int threshold;
//output
public Set<Point> set = new HashSet<Point>();
public int minX = Integer.MAX_VALUE, minY = minX, maxX = 0, maxY = 0;
FloodFill(Canvas c, BufferedImage source, Point p, int threshold)
{
this.source = source;
this.threshold = threshold;
if (p.x < 0 || p.y < 0 || p.x >= source.getWidth() || p.y >= source.getHeight()) return;
targetRGB = source.getRGB(p.x,p.y);
floodFill(p,c);
}
protected void floodFill(Point start, Canvas c)
{
// Create a queue of points to fill around
Queue<Point> q = new ArrayDeque<Point>();
// Bounds checking
if (start.x < 0 || start.y < 0 || start.x >= source.getWidth()
|| start.y >= source.getHeight())
{
if (c.renderMode != RenderMode.TILED) return;
System.out.print("out of bounds\n");
start = new Point((start.x + source.getWidth()) % source.getWidth(),
(start.y + source.getHeight()) % source.getHeight());
}
q.add(start);
while (!q.isEmpty())
{
Point n = q.remove();
if (!needsFill(n)) continue;
Point w = new Point(n), e = new Point(n);
do
w.x--;
while (needsFill(w));
do
e.x++;
while (needsFill(e));
w.x++;
if (w.x < minX) minX = w.x;
if (e.x - 1 > maxX) maxX = e.x - 1;
if (n.y < minY) minY = n.y;
if (n.y > maxY) maxY = n.y;
if (c.renderMode != RenderMode.TILED)
{
for (int x = w.x; x < e.x; x++)
set.add(new Point(x,n.y));
if (n.y - 1 >= 0) for (int x = w.x; x < e.x; x++)
if (needsFillInBound(new Point(x,n.y - 1))) q.add(new Point(x,n.y - 1));
if (n.y + 1 < source.getHeight()) for (int x = w.x; x < e.x; x++)
if (needsFillInBound(new Point(x,n.y + 1))) q.add(new Point(x,n.y + 1));
}
else
{
for (int x = w.x; x < e.x; x++)
set.add(new Point(x,n.y));
// Wrap horizontally
if (w.x == 0)
{
Point wrap_p = new Point(maxX = source.getWidth() - 1,w.y);
if (needsFillInBound(wrap_p)) q.add(wrap_p);
}
else if (e.x == source.getWidth())
{
Point wrap_p = new Point(minX = 0,e.y);
if (needsFillInBound(wrap_p)) q.add(wrap_p);
}
// Wrap vertically
if (n.y - 1 >= 0)
for (int x = w.x; x < e.x; x++)
{
if (needsFillInBound(new Point(x,n.y - 1))) q.add(new Point(x,n.y - 1));
}
else
{
int h1 = source.getHeight() - 1;
for (int x = w.x; x < e.x; x++)
if (needsFillInBound(new Point(x,h1))) q.add(new Point(x,h1));
}
if (n.y + 1 < source.getHeight())
for (int x = w.x; x < e.x; x++)
{
if (needsFillInBound(new Point(x,n.y + 1))) q.add(new Point(x,n.y + 1));
}
else
for (int x = w.x; x < e.x; x++)
if (needsFillInBound(new Point(x,0))) q.add(new Point(x,0));
}
}
}
protected boolean needsFillInBound(Point p)
{
// Return false if we've already filled this pixel
if (set.contains(p)) return false;
// Return whether this pixel matches the color we're filling
return source.getRGB(p.x,p.y) == targetRGB; // TODO: Add threshold here
}
protected boolean needsFill(Point p)
{
if (p.x < 0 || p.y < 0 || p.x >= source.getWidth() || p.y >= source.getHeight())
return false;
return needsFillInBound(p);
}
}
public static class EdgeDetect
{
public Set<Point> set = new HashSet<Point>();
public EdgeDetect(FloodFill body, Canvas cv)
{
if (cv.renderMode != RenderMode.TILED)
for (Point p : body.set)
{
Point c = new Point(p.x,p.y);
c.x -= 1;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
c.x += 2;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
c.x -= 1;
c.y -= 1;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
c.y += 2;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
}
else
for (Point p : body.set)
{
Dimension is = cv.getImageSize();
Point c = new Point(p.x,p.y);
c.x = (c.x + is.width - 1) % is.width;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
c.x = (c.x + is.width + 2) % is.width;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
c.x = p.x;
c.y = (c.y + is.height - 1) % is.height;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
c.y = (c.y + is.height + 2) % is.height;
if (!body.set.contains(c))
{
set.add(p);
continue;
}
}
}
}
}