Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
381 views
in Technique[技术] by (71.8m points)

java - Is there a "fill" function for arbitrary shapes in javafx?

I need to know in which way I can color the following image (PNG) by using JavaFX. This image is currently included in a ImageView of JavaFX:

image with different sections

I want to color region 1 blue, the second one red, and the last two purple. How can I do this in JavaFX? Isn't there some kind of function as in Windows Paint? (You know, the painting bucket that fills a certain area with a color between borders).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Suggested Approach

You can use a flood fill algorithm.

filled

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...