Suggested Approach
You can use a flood fill algorithm.
Sample Code
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Stack;
public class UnleashTheKraken extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(final Stage stage) {
Image original = new Image(
"http://s12.postimg.org/wofhjvy2h/image_2.jpg"
);
WritableImage updateable = new WritableImage(
original.getPixelReader(),
(int) original.getWidth(),
(int) original.getHeight()
);
Kraken kraken = new Kraken(updateable, Color.WHITE);
kraken.unleash(new Point2D(40, 40), Color.BLUE);
kraken.unleash(new Point2D(40, 100), Color.RED);
kraken.unleash(new Point2D(100, 100), Color.GREEN);
kraken.unleash(new Point2D(120, 40), Color.YELLOW);
ImageView originalView = new ImageView(original);
ImageView filledView = new ImageView(updateable);
HBox layout = new HBox(10, originalView, filledView);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
class Kraken {
private final WritableImage image;
private final Color colorToFill;
// tolerance for color matching (on a scale of 0 to 1);
private final double E = 0.3;
public Kraken(WritableImage image, Color colorToFill) {
this.image = image;
this.colorToFill = colorToFill;
}
public void unleash(Point2D start, Color color) {
PixelReader reader = image.getPixelReader();
PixelWriter writer = image.getPixelWriter();
Stack<Point2D> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
Point2D point = stack.pop();
int x = (int) point.getX();
int y = (int) point.getY();
if (filled(reader, x, y)) {
continue;
}
writer.setColor(x, y, color);
push(stack, x - 1, y - 1);
push(stack, x - 1, y );
push(stack, x - 1, y + 1);
push(stack, x , y + 1);
push(stack, x + 1, y + 1);
push(stack, x + 1, y );
push(stack, x + 1, y - 1);
push(stack, x, y - 1);
}
}
private void push(Stack<Point2D> stack, int x, int y) {
if (x < 0 || x > image.getWidth() ||
y < 0 || y > image.getHeight()) {
return;
}
stack.push(new Point2D(x, y));
}
private boolean filled(PixelReader reader, int x, int y) {
Color color = reader.getColor(x, y);
return !withinTolerance(color, colorToFill, E);
}
private boolean withinTolerance(Color a, Color b, double epsilon) {
return
withinTolerance(a.getRed(), b.getRed(), epsilon) &&
withinTolerance(a.getGreen(), b.getGreen(), epsilon) &&
withinTolerance(a.getBlue(), b.getBlue(), epsilon);
}
private boolean withinTolerance(double a, double b, double epsilon) {
return Math.abs(a - b) < epsilon;
}
}
}
Answers to additional questions
But wouldn't the image be colored pixel by pixel?
Yes, that's the point, you need to shade the pixels. Everything in computer graphics with bitmapped displays eventually comes down to coloring pixels.
Is this an efficient way in coloring?
It's instantaneous (as far as I can tell) on the sample image you provided. Space-wise it takes up some memory, but all such algorithms will use memory. The sample code I provided is not the most efficient flood fill shading algorithm which could be devised (time or space wise). The wikipedia page I linked has alternate more efficient (and more complicated) algorithms you could apply if you needed to.
Alternate Approach
If you have a cut-out stencil shape for each area, you could stack the stencils and apply ColorAdjust effects to them (such as in: How to change color of image in JavaFX). The ColorAdjust is (likely) a hardware accelerated effect. This alternate is not a general approach though as it requires you to know the stencil shapes.