package king.lib.util;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.Color;
import java.util.ArrayList;
/**
* Drawing class which provides flood fill functionality. Found
* on http://www.javagaming.org forum and adapted to be more generic. Antialiasing
* and texture filling capability added.
*
* @author kingaschi (Christph Aschwanden - king@kingx.com)
* @author moogie (Javagaming Forum)
* @author tom (Javagaming Forum)
* @since April 26, 2005
*/
public final class FloodFill {
/**
* Line info class for linear non-recursive fill.
*
* @author king
* @since April 27, 2005
*/
class LineInfo {
/** The left position. */
int left;
/** The right position. */
int right;
/** The y position. */
int y;
/**
* Sets the line info.
*
* @param left Previous left position.
* @param right Previous right position.
* @param y Y position.
*/
void setInfo(int left, int right, int y) {
this.left = left;
this.right = right;
this.y = y;
}
}
/** The array used for fast flood fill. Instantiated only once to improve performance. */
private ArrayList<LineInfo> linearNRTodo = new ArrayList<LineInfo>();
/** The index into linear non-recursive fill. */
private int index;
/** True, if antialised fill should be used. */
private boolean antialiased = true;
/** The raw image data to fill. */
private int[] image;
/** The raw mask image data with the borders. */
private int[] maskImage;
/** The start x position for the fill. */
private int startX;
/** The start y position for the fill. */
private int startY;
/** The fill color to use for the original image. */
private int fillColor;
/** The fill color to use for the mask image. */
private int maskColor;
/** The pattern fill color to use. */
private int patternColor;
/** The width of the chessboard pattern. */
private int patternWidth;
/** The height of the chessboard pattern. */
private int patternHeight;
/** The color to replace with the fill color. */
private int startColor;
/** The width of the image to fill. */
private int width;
/** The height of the image to fill. */
private int height;
/** The result image. */
private BufferedImage bufferedImage;
/** The mask image used. */
private BufferedImage bufferedMaskImage;
/**
* Constructor for flood fill, requires the image for filling operation.
*
* @param imageToFill The image used for filling.
*/
public FloodFill(Image imageToFill) {
this(imageToFill, null);
}
/**
* Constructor for flood fill, requires the image and mask for filling operation.
*
* @param imageToFill The image used for filling.
* @param maskImage The image containing the border lines.
*/
public FloodFill(Image imageToFill, Image maskImage) {
// sets image to fill
setImage(imageToFill);
// sets the mask
setMask(maskImage);
}
/**
* Returns true, if antialiased filling is used.
*
* @return True, for antialiased filling.
*/
public boolean isAntialiased() {
return this.antialiased;
}
/**
* Sets if antialiased filling is used.
*
* @param antialiased True, for antialiased filling.
*/
public void setAntialiased(boolean antialiased) {
this.antialiased = antialiased;
}
/**
* Returns the width of the fill area.
*
* @return The width of the fill area.
*/
public int getWidth() {
return this.width;
}
/**
* Returns the height of the fill area.
*
* @return The height of the fill area.
*/
public int getHeight() {
return this.height;
}
/**
* Returns the image that was filled.
*
* @return The image that was filled.
*/
public BufferedImage getImage() {
return bufferedImage;
}
/**
* Sets the image to be filled.
*
* @param imageToFill The image to be filled.
*/
public void setImage(Image imageToFill) {
// copy image to fill into buffered image first
width = imageToFill.getWidth(null);
height = imageToFill.getHeight(null);
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = bufferedImage.getGraphics();
g.drawImage(imageToFill, 0, 0, null);
// get fill image
this.image = ((DataBufferInt)bufferedImage.getRaster().getDataBuffer()).getData();
}
/**
* Returns the mask containing the fill borders.
*
* @return The mask with the fill borders.
*/
public BufferedImage getMask() {
return bufferedMaskImage;
}
/**
* Sets the mask image which contains the borders.
*
* @param maskImage The mask image to set. If null, the image to fill is used as
* the mask.
*/
public void setMask(Image maskImage) {
this.bufferedMaskImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = this.bufferedMaskImage.getGraphics();
if (maskImage == null) {
// if no mask, use the original image to fill
g.drawImage(this.bufferedImage, 0, 0, null);
}
else {
// if mask, use it
g.drawImage(maskImage, 0, 0, null);
}
this.maskImage = bufferedMaskImage.getRGB(0, 0, width, height, null, 0, width);
}
/**
* Flood fills parts of an image.
*
* @param x The x coordinate to start filling.
* @param y The y coordinate to start filling.
* @param color The new fill color.
*/
public void fill(int x, int y, Color color) {
this.startX = x;
this.startY = y;
this.fillColor = color.getRGB();
this.maskColor = color.getRGB(); // the fill color for the mask
this.startColor = this.maskImage[startY * width + startX];
if (fillColor != startColor) {
floodFill();
}
}
/**
* Fills a bounded area of an image. Uses a chessboard pattern. The width in pixels
* of the chessboard pattern can be specified.
*
* @param x The x coordinate to start filling.
* @param y The y coordinate to start filling.
* @param color The new fill color.
* @param patternColor The color of the pattern to use.
* @param patternWidth The width of the chessboard pattern.
* @param patternHeight The height of the chessboard pattern.
*/
public void fill(int x, int y, Color color, Color patternColor, int patternWidth, int patternHeight) {
this.startX = x;
this.startY = y;
this.fillColor = color.getRGB();
this.patternColor = patternColor.getRGB();
this.patternWidth = (patternWidth > 0) ? patternWidth : Integer.MAX_VALUE;
this.patternHeight = (patternHeight > 0) ? patternHeight : Integer.MAX_VALUE;
this.startColor = this.maskImage[startY * width + startX];
// the fill color for the mask
this.maskColor = ((this.fillColor >> 2) & 0x3F3F3F3F)
+ ((this.patternColor >> 1) & 0x7F7F7F7F);
if (this.maskColor != this.startColor) {
// mask color ok (=different)
floodFill2();
}
else {
// create new mask color first
this.maskColor = ((this.fillColor >> 1) & 0x7F7F7F7F)
+ ((this.patternColor >> 2) & 0x3F3F3F3F);
floodFill2();
}
}
/**
* Fills the line at (x, y). Then fills the line above and below the current line.
* The border is defined as any color except the start color. Non-recursive version,
* doesn't have JVM stack size limitations.
*/
private void floodFill() {
// init stack
linearNRTodo.clear();
index = 0;
floodFill(startX, startY);
// loop through todo list
while (index < linearNRTodo.size()) {
// get loop data
LineInfo lineInfo = linearNRTodo.get(index);
index++;
int y = lineInfo.y;
int left = lineInfo.left;
int right = lineInfo.right;
// check top
if (y > 0) {
int yOff = (y - 1) * width;
int x = left;
while (x <= right) {
if (maskImage[yOff + x] == startColor) {
x = floodFill(x, y - 1);
}
else {
// fill antialised if allowed
if (antialiased) {
int antialiasedColor = maskImage[yOff + x];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + x] = antialiasedColor;
}
}
}
x++;
}
}
// check bottom
if (y < height - 1) {
int yOff = (y + 1) * width;
int x = left;
while (x <= right) {
if (maskImage[yOff + x] == startColor) {
x = floodFill(x, y + 1);
}
else {
// fill antialised if allowed
if (antialiased) {
int antialiasedColor = maskImage[yOff + x];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + x] = antialiasedColor;
}
}
}
x++;
}
}
}
}
/**
* Fills the line at (x, y). And adds to the stack.
*
* @param x The x-coordinate of the start position.
* @param y The y-coordinate of the start position.
* @return Right.
*/
private int floodFill(int x, int y) {
int yOff = y * width;
// fill left of (x,y) until border or edge of image
int left = x;
do {
image[yOff + left] = fillColor;
maskImage[yOff + left] = maskColor;
left--;
}
while ((left >= 0) && (maskImage[yOff + left] == startColor));
// fill antialised if allowed
if ((antialiased) && (left >= 0)) {
int antialiasedColor = maskImage[yOff + left];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + left] = antialiasedColor;
}
}
left++;
// fill right of (x, y) until border or edge of image
int right = x;
do {
image[yOff + right] = fillColor;
maskImage[yOff + right] = maskColor;
right++;
}
while ((right < width) && (maskImage[yOff + right] == startColor));
// fill antialised if allowed
if ((antialiased) && (right < width)) {
int antialiasedColor = maskImage[yOff + right];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + right] = antialiasedColor;
}
}
right--;
// add to stack
if (index == 0) {
LineInfo lineInfo = new LineInfo();
lineInfo.setInfo(left, right, y);
linearNRTodo.add(lineInfo);
}
else {
index--;
linearNRTodo.get(index).setInfo(left, right, y);
}
// return right position
return right;
}
/**
* Fills the line at (x, y). Then fills the line above and below the current line.
* The border is defined as any color except the start color. Non-recursive version,
* doesn't have JVM stack size limitations.
*/
private void floodFill2() {
// init stack
linearNRTodo.clear();
index = 0;
floodFill2(startX, startY);
// loop through todo list
while (index < linearNRTodo.size()) {
// get loop data
LineInfo lineInfo = linearNRTodo.get(index);
index++;
int y = lineInfo.y;
int left = lineInfo.left;
int right = lineInfo.right;
// check top
if (y > 0) {
int yOff = (y - 1) * width;
int x = left;
while (x <= right) {
if (maskImage[yOff + x] == startColor) {
x = floodFill2(x, y - 1);
}
else {
// fill antialised if allowed
if (antialiased) {
int antialiasedColor = maskImage[yOff + x];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor2(x, y - 1) >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + x] = antialiasedColor;
}
}
}
x++;
}
}
// check bottom
if (y < height - 1) {
int yOff = (y + 1) * width;
int x = left;
while (x <= right) {
if (maskImage[yOff + x] == startColor) {
x = floodFill2(x, y + 1);
}
else {
// fill antialised if allowed
if (antialiased) {
int antialiasedColor = maskImage[yOff + x];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor2(x, y + 1) >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + x] = antialiasedColor;
}
}
}
x++;
}
}
}
}
/**
* Fills the line at (x, y). And adds to the stack.
*
* @param x The x-coordinate of the start position.
* @param y The y-coordinate of the start position.
* @return Right.
*/
private int floodFill2(int x, int y) {
int yOff = y * width;
// fill left of (x,y) until border or edge of image
int left = x;
do {
image[yOff + left] = fillColor2(left, y);
maskImage[yOff + left] = maskColor;
left--;
}
while ((left >= 0) && (maskImage[yOff + left] == startColor));
// fill antialised if allowed
if ((antialiased) && (left >= 0)) {
int antialiasedColor = maskImage[yOff + left];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor2(left, y) >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + left] = antialiasedColor;
}
}
left++;
// fill right of (x, y) until border or edge of image
int right = x;
do {
image[yOff + right] = fillColor2(right, y);
maskImage[yOff + right] = maskColor;
right++;
}
while ((right < width) && (maskImage[yOff + right] == startColor));
// fill antialised if allowed
if ((antialiased) && (right < width)) {
int antialiasedColor = maskImage[yOff + right];
antialiasedColor = (antialiasedColor >> 1) & 0x7F7F7F7F;
antialiasedColor = antialiasedColor + ((fillColor2(right, y) >> 1) & 0x7F7F7F7F);
if (antialiasedColor != startColor) {
image[yOff + right] = antialiasedColor;
}
}
right--;
// add to stack
if (index == 0) {
LineInfo lineInfo = new LineInfo();
lineInfo.setInfo(left, right, y);
linearNRTodo.add(lineInfo);
}
else {
index--;
linearNRTodo.get(index).setInfo(left, right, y);
}
// return right position
return right;
}
/**
* Returns the fill color for a given x and y value.
*
* @param x The x position to return the color for.
* @param y The y position to return the color for.
* @return The color for the given position.
*/
private int fillColor2(int x, int y) {
x /= this.patternWidth;
y /= this.patternHeight;
if ((x + y) % 2 == 0) {
return this.fillColor;
}
else {
return this.patternColor;
}
}
}
|