Flood Fill Algorithm Resulting in Black Image - java

I'm constructing a flood fill algorithm that will hopefully, eventually, find a face in the center of a photograph based on the color at the exact center, and similar colors surrounding that. At the moment, however, my algorithm should take in any color within the bounds of the int array and transfer it over to a holder array, essentially making a copy of the original image. But this isn't working, and is resulting in a black image when I run it. Can anyone see the problem I'm missing?
public class TemplateMaker {
public static void main(String[] args) throws IOException {
importPhoto();
}
public static void importPhoto() throws IOException {
File imgPath = new File("/Pictures/BaseImage.JPG");
BufferedImage bufferedImage = ImageIO.read(imgPath);
establishArray(bufferedImage);
}
public static void establishArray(BufferedImage bufferedImage) throws IOException {
//byte[] pixels = hugeImage.getData();
int width = bufferedImage.getWidth();
System.out.println(width);
int height = bufferedImage.getHeight();
System.out.println(height);
int[][] result = new int[height][width];
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++) {
result[i][j] = bufferedImage.getRGB(j, i);
}
findFace(result);
}
public static void findFace(int[][] image) throws IOException {
int height = image.length;
int width = image[0].length;
Color centerStart = new Color(image[height / 2][width / 2], true);
System.out.println(centerStart.getRGB());
System.out.println(Color.blue.getRGB());
int[][] filled = new int[height][width];
floodFill(height / 2, width / 2, centerStart, image, filled, height, width);
//construct the filled array as image.
BufferedImage bufferImage2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
bufferImage2.setRGB(y, x, filled[x][y]);
}
}
//save filled array as image file
File outputfile = new File("/Pictures/saved.jpg");
ImageIO.write(bufferImage2, "jpg", outputfile);
}
public static int[][] floodFill(int x, int y, Color targetColor, int[][] image, int[][] filled, int height, int width) {
//execute something similar once algorithm works.
// if (image[x][y] < targetColor.getRGB()/2 || image[x][y] > targetColor.getRGB()*2) return filled;
if (image[x][y] == Color.blue.getRGB()) {
return filled;
}
if (image.length < 0 || image[0].length < 0 || image.length >= height || image[0].length >= width) {
return filled;
}
filled[x][y] = image[x][y];
image[x][y] = Color.blue.getRGB();
floodFill(x - 1, y, targetColor, image, filled, height, width);
floodFill(x + 1, y, targetColor, image, filled, height, width);
floodFill(x, y - 1, targetColor, image, filled, height, width);
floodFill(x, y + 1, targetColor, image, filled, height, width);
return filled;
}
}

You create int[][] called filled and then call floodFill(...) which returns without doing anything to the array. image.length is always equal to height and image[0].length is always equal to width, so it always returns from the second if statement.
You then build a BufferedImage from that blank array and write it to a file. All of the values in the array are initialized to 0, which gives you black.
Changing the for loop in findFace(..) to the below will save out the original image from your holder array.
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
bufferImage2.setRGB(y, x, image[x][y]);
}
}
But I'm not sure if this is what you're asking or not.
Edit: Try this out and see if it sends you in the right direction:
public static int[][] floodFill(int x, int y, Color targetColor, int[][] image, int[][] filled, int height, int width) {
//execute something similar once algorithm works.
// if (image[x][y] < targetColor.getRGB()/2 || image[x][y] > targetColor.getRGB()*2) return filled;
if (image[x][y] == Color.blue.getRGB()) {
System.out.println("returned if 1");
return filled;
}
/*if (image.length < 0 || image[0].length < 0 || image.length >= height || image[0].length >= width) {
return filled;
}*/
filled[x][y] = image[x][y];
image[x][y] = Color.blue.getRGB();
if (x - 1 <= 0 && y < width) {
floodFill(x - 1, y, targetColor, image, filled, height, width);
}
if(x + 1 < height && y >= 0 && y < width) {
floodFill(x + 1, y, targetColor, image, filled, height, width);
}
if(x >= 0 && x < height && y - 1 <= 0) {
floodFill(x, y - 1, targetColor, image, filled, height, width);
}
if(x >= 0 && x < height && y + 1 < width) {
floodFill(x, y + 1, targetColor, image, filled, height, width);
}
return filled;
}

Related

Problems with BufferedImage SubImages

I am currently trying to get my java program to load separate sprites from a sprite sheet. When I try to get The yellow square in this sprite sheet
.
However, what I get is this
.
Prior to this I was able to load and render an entire PNG file without problems, and I am unsure the reason for it not working. I put snippets of code that I use to load images, render images, and getting the subimage. I'm still somewhat new to java, so I'm not sure if there is a better solution to this
//The code I use to load a PNG as a BufferedImage
public BufferedImage loadImage(String path) {
try {
BufferedImage loadedImage = ImageIO.read(new FileInputStream(path));
BufferedImage formattedImage = new BufferedImage(loadedImage.getWidth(), loadedImage.getHeight(), BufferedImage.TYPE_INT_RGB);
formattedImage.getGraphics().drawImage(loadedImage, 0, 0, null);
return formattedImage;
} catch (IOException e) {
e.printStackTrace();
return null;
}
//The code I use to render an image onto the screen
public void renderImage(BufferedImage image, int xpos, int ypos) {
int[] imgpixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
if (!(x + xpos > getScreenWidth() - 1) && !(y + ypos > getScreenHeight() - 1) && !(x + xpos < 0) && !(y + ypos < 0)) {
pixels[(x + xpos) + (y + ypos) * getScreenWidth()] = imgpixels[x + y * image.getWidth()];
}
}
}
}
//the code I used to get the subimage
public BufferedImage getImage(int index) {
int i = index % (row * col);
int x = tileX * (i % col);
int y = tileY * (i % row);
return spriteSheet.getSubimage(x, y, tileX, tileY);
//still does the same thing if I were to put in spriteSheet.getSubimage(0, 0, 16, 16)
}

Converting monochrome image to minimum number of 2d shapes

Basically, what I need to do is take a 2d array of bitflags and produce a list of 2d rectangles to fill the entire area with the minimum number of total shapes required to perfectly fill the space. I am doing this to convert a 2d top-down monochrome of a map into 2d rectangle shapes which perfectly represent the passed in image which will be used to generate a platform in a 3d world. I need to minimize the total number of shapes used, because each shape will represent a separate object, and flooding it with 1 unit sized squares for each pixel would be highly inefficient for that engine.
So far I have read in the image, processed it, and filled a two dimensional array of booleans which tells me if the pixel should be filled or unfilled, but I am unsure of the most efficient approach of continuing.
Here is what I have so far, as reference, if you aren't following:
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
int[] rgb = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), new int[bi.getWidth() * bi.getHeight()], 0, bi.getWidth());
Origin origin = new Origin(bi.getWidth() / 2, bi.getHeight() / 2);
boolean[][] flags = new boolean[bi.getWidth()][bi.getHeight()];
for (int y = 0; y < bi.getHeight(); y++) {
for (int x = 0; x < bi.getWidth(); x++) {
int index = y * bi.getWidth() + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
List<Rectangle> list = new ArrayList();
//Fill list with rectangles
}
}
White represents no land. Black or Red represents land. The check for the red pixel marks the origin position of map, which was just for convenience and the rectangles will be offset by the origin position if it is found.
Edit: The processing script does not need to be fast, the produced list of rectangles will be dumped and that will be what will be imported and used later, so the processing of the image does not need to be particularly optimized, it doesn't make a difference.
I also just realized that expecting a 'perfect' solution is expecting too much, since this would qualify as a 'knapsack problem' of the multidimensionally constrained variety, if I am expecting exactly the fewest number of rectangles, so simply an algorithm that produces a minimal number of rectangles will suffice.
Here is a reference image for completion:
Edit 2: It doesn't look like this is such an easy thing to answer given no feedback yet, but I have started making progress, but I am sure I am missing something that would vastly reduce the number of rectangles. Here is the updated progress:
static int mapWidth;
static int mapHeight;
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
System.out.println("Reading image...");
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
System.out.println("Complete!");
System.out.println("Interpreting image...");
mapWidth = bi.getWidth();
mapHeight = bi.getHeight();;
int[] rgb = bi.getRGB(0, 0, mapWidth, mapHeight, new int[mapWidth * mapHeight], 0, mapWidth);
Origin origin = new Origin(mapWidth / 2, mapHeight / 2);
boolean[][] flags = new boolean[mapWidth][mapHeight];
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
int index = y * mapWidth + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
System.out.println("Complete!");
System.out.println("Processing...");
//Get Rectangles to fill space...
List<Rectangle> rectangles = getRectangles(flags, origin);
System.out.println("Complete!");
float rectangleCount = rectangles.size();
float totalCount = mapHeight * mapWidth;
System.out.println("Total units: " + (int)totalCount);
System.out.println("Total rectangles: " + (int)rectangleCount);
System.out.println("Rectangle reduction factor: " + ((1 - rectangleCount / totalCount) * 100.0) + "%");
System.out.println("Dumping data...");
try {
file = new File(file.getParentFile(), file.getName() + "_Rectangle_Data.txt");
if(file.exists()){
file.delete();
}
file.createNewFile();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
for(Rectangle rect: rectangles){
bw.write(rect.x + "," + rect.y + "," + rect.width + ","+ rect.height + "\n");
}
bw.flush();
bw.close();
} catch (Exception ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
System.out.println("Complete!");
}else{
System.out.println("Error!");
}
}
public static void clearRange(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
flags[x][y] = false;
}
}
}
public static boolean checkIfFilled(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
if (!flags[x][y]) {
return false;
}
}
}
return true;
}
public static List<Rectangle> getRectangles(boolean[][] flags, Origin origin) {
List<Rectangle> rectangles = new ArrayList();
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
if (flags[x][y]) {
int maxWidth = 1;
int maxHeight = 1;
Loop:
//The search size limited to 400x400 so it will complete some time this century.
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
//Search also in the opposite direction
Loop:
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
rectangles.add(new Rectangle(x - origin.x, y - origin.y, maxWidth, maxHeight));
clearRange(flags, x, y, maxWidth, maxHeight);
}
}
}
return rectangles;
}
My current code's search for larger rectangles is limited to 400x400 to speed up testing, and outputs 17,979 rectangles, which is a 99.9058% total reduction of rectangles if I treated each pixel as a 1x1 square(19,095,720 pixels). So far so good.

Trying two different methods to transform a picture - JavaFX

So Im still learning Java. Now Im learning JavaFX.
I have a picture of a tree. And I want to try two different method. the first method I use was using unary operator to turn the image colour to grey.
Now I want to try a second method using the ColourTransformer interface that I made to get a 10 pixel wide gray frame replacing the pixels on the border of an image.
This is what I have done. for the second method, im not quite sure how to specify the pixel. Any suggestions?
this is what I have done
public class ColourFilter extends Application {
//Using Unary Operator to transform image to grayscale - Method 1
public static Image transform(Image in, UnaryOperator<Color> f) {
int width = (int) in.getWidth();
int height = (int) in.getHeight();
WritableImage out = new WritableImage(
width, height);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
out.getPixelWriter().setColor(x, y,
f.apply(in.getPixelReader().getColor(x, y)));
return out;
}
public static <T> UnaryOperator<T> compose(UnaryOperator<T> op1, UnaryOperator<T> op2) {
return t -> op2.apply(op1.apply(t));
}
//Using ColourTransformer interface to get 10 pixel wide gray frame replacing the pixels on the border of an image - Method 2
public static Image transform(Image in, ColourTransformer f) {
int width = (int) in.getWidth();
int height = (int) in.getHeight();
WritableImage out = new WritableImage(
width, height);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
out.getPixelWriter().setColor(x, y, f.apply(x, y, in.getPixelReader().getColor(x, y)));
return out;
}
#FunctionalInterface
interface ColourTransformer {
Color apply(int x, int y, Color colorAtXY);
}
public void start(Stage stage) {
Image image = new Image("amazing-trees.jpg");
Image image2 = transform(image, Color::brighter);
Image image3 = transform(image2, Color::grayscale);
// alternative to two previous image transforms -- composition
//Image image3 = transform(image, compose(Color::brighter, Color::grayscale));
stage.setScene(new Scene(new VBox(
new ImageView(image),
// new ImageView(image2),
new ImageView(image3))));
stage.show();
}
}
As the compile error message says, you are trying to call
f.apply(Color);
where f is a ColourTransformer: however you defined the apply method in ColorTransformer with three parameters: apply(int, int, Color).
You need to replace
f.apply(in.getPixelReader().getColor(x, y))
with
f.apply(x, y, in.getPixelReader().getColor(x, y))
i.e.
public static Image transform(Image in, ColourTransformer f) {
int width = (int) in.getWidth();
int height = (int) in.getHeight();
WritableImage out = new WritableImage(
width, height);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
out.getPixelWriter().setColor(x, y, f.apply(x, y, in.getPixelReader().getColor(x, y)));
return out;
}
You can get the 10-pixel gray border by doing:
Image image = new Image("amazing-trees.jpg");
int width = (int) image.getWidth();
int height = (int) image.getHeight();
Image framedImage = transform(image, (x, y, color) -> {
if (x < 10 || y < 10 || width - x < 10 || height - y < 10) {
return Color.GRAY ;
} else return color ;
});

Can't draw BufferedImage into another BufferedImage with scale

Please advise.
I'm trying to draw input BufferedImage into larger output BufferedImage (with scaling). Please, take a look at the following code:
public class Main {
public void print(BufferedImage img, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
System.out.print(img.getRGB(x, y) + " ");
}
System.out.println("");
}
}
public static void main(String[] args) {
Main app = new Main();
// create input image
int inputWidth = 2;
int inputHeight = 2;
BufferedImage inputImg = new BufferedImage(inputWidth, inputHeight, BufferedImage.TYPE_INT_ARGB);
// fill input image
for (int y = 0; y < inputHeight; y++) {
for (int x = 0; x < inputWidth; x++) {
inputImg.setRGB(x, y, y * inputWidth * (1 << 16) + x);
}
}
// print
app.print(inputImg, inputWidth, inputHeight);
// create output image
int outputWidth = 4;
int outputHeight = 4;
BufferedImage outputImg = new BufferedImage(outputWidth, outputHeight, BufferedImage.TYPE_INT_ARGB);
// draw inputImg into outputImg
Graphics2D g = outputImg.createGraphics();
g.drawImage(inputImg, 0, 0, outputImg.getWidth(), outputImg.getHeight(), 0, 0, inputImg.getWidth(), inputImg.getHeight(), null);
// print
app.print(outputImg, outputImg.getWidth(), outputImg.getHeight());
}
}
Execution produces the following output:
0 1
131072 131073
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
Seems like Graphics2D object works, because I'm able to draw, for example, a line calling the drawLine function. So, I think the inputImg is the source of the issue, but I can't figure out what's wrong.
UPDATE:
I've tried to use AffineTransform, but it didn't help, unfortunately.
Graphics2D g = outputImg.createGraphics();
AffineTransform at = new AffineTransform();
at.setToIdentity();
at.scale(2, 2);
g.drawImage(inputImg, at, null);
To me, this seems to be an issue with the color calculation you're using...
When I change...
inputImg.setRGB(x, y, y * inputWidth * (1 << 16) + x);
to...
int rgb = y * inputWidth * (1 << 16) + x;
inputImg.setRGB(x, y, new Color(rgb).getRGB());
I get a result, albeit a black dot. This suggests to me that by default, your calculation is generating a alpha value of 0
This can be born out in the output that they produce:
My method generates
-16777216 -16777215
-16646144 -16646143
Yours generates
0 1
131072 131073
Now, frankly, this is why I don't do this kind of calculation, not when a API is available to do it for me - but I be dumb ;P
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class Main {
public void print(BufferedImage img, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
System.out.print(img.getRGB(x, y) + " ");
}
System.out.println("");
}
}
public static void main(String[] args) {
Main app = new Main();
// create input image
int inputWidth = 2;
int inputHeight = 2;
BufferedImage inputImg = new BufferedImage(inputWidth, inputHeight, BufferedImage.TYPE_INT_ARGB);
// fill input image
System.out.println(inputWidth + "x" + inputHeight);
Color color = Color.RED;
for (int y = 0; y < inputHeight; y++) {
for (int x = 0; x < inputWidth; x++) {
int rgb = y * inputWidth * (1 << 16) + x;
inputImg.setRGB(x, y, new Color(rgb).getRGB());
}
}
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(inputImg)));
// print
app.print(inputImg, inputWidth, inputHeight);
// create output image
int outputWidth = 4;
int outputHeight = 4;
BufferedImage outputImg = new BufferedImage(outputWidth, outputHeight, BufferedImage.TYPE_INT_ARGB);
// draw inputImg into outputImg
Graphics2D g = outputImg.createGraphics();
g.drawImage(inputImg, 0, 0, outputImg.getWidth(), outputImg.getHeight(), 0, 0, inputImg.getWidth(), inputImg.getHeight(), null);
g.dispose();
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(outputImg)));
// print
app.print(outputImg, outputImg.getWidth(), outputImg.getHeight());
}
}

Input an image from some saved location on computer

I have a draw function
public void drawBoard(Graphics g) {
int height = this.getHeight();
int width = this.getWidth();
int dx = width / 7;
int dy = height / 6;
for (int x = 0, row = 0; x <= width && row < gameboard.length; row++, x += dx) {
for (int col = 0, y = 0; y <= height&& col < gameboard[0].length; y += dy, col++) {
if (gameboard[row][col] == 0) {
g.setColor(Color.GRAY);
g.fillOval(y, x, dy, dx);
} else if (gameboard[row][col] == 1) {
g.setColor(Color.RED);
g.fillOval(y, x, dy, dx);
} else if(gameboard[row][col] == 1){
g.setColor(Color.BLACK);
g.fillOval(y, x, dy, dx);
} else if(gameboard[row][col]==3){
}else if(gameboard[row][col]==4){
}else if(gameboard[row][col]==5){
}else if(gameboard[row][col]==6){
}else if(gameboard[row][col]==7){
}else if(gameboard[row][col]==8){
}else if(gameboard[row][col]==9){
}
}
}
}
However for when gameboard[row][col]=3,4,...9 I want it to change that slot into a picture downloaded from the web. How do I do that?
I would prefer to do it without a URL definition and simply a get Document like thing in html where I have the photos saved in a file
First, you need to get your image as a BufferedImage. I suggest using the ImageIO class:
String imageFileName = "myTestFile.jpg"
BufferedImage img = ImageIO.read(((new File(imageFileName)).toURI()).toURL());
Next, you want to draw your image using the Graphics.drawImage() API. Your comment indicates that you think you'll need to scale the image, so use the appropriate drawImage method to do that. Using your Graphics object g from your code above, this might look like::
int oldWidth = img.getWidth();
int oldHeight = img.getHeight();
int newWidth = 10; //You decide this...
int newHeight = 10; //You decide this too...
g.drawImage(img, 0, 0, oldWidth, newWidth, 0, 0, newWidth, newHeight, null);
Oracle themselves have an applet which demonstrates the code for this approach, along with a number of other common operations you might want to do on images in their ImageDrawingApplet.java code

Categories

Resources