JavaFX 渲染/图像处理

标签 java image-processing javafx bresenham

我制作了一个生成长阴影的 JavaFX 小应用程序。此时我正在努力处理渲染(见图)。

  1. 矩形角上缺失的线条似乎很难修复。更改应用操作的循环会弄乱其他形状的阴影(例如圆形)。
  2. “a”处的故障与 Bresenham algorithm 有关,我猜。(?)

其他信息:

更改图像分辨率没有任何区别:问题不断出现。

问题:

如何解决? SDK 提供了一些有用的东西吗?我必须重写代码吗?

Screenshot

代码

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import javax.imageio.ImageIO;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;


public class Main extends Application {

    private PrintWriter writer;

    private String colorObjFilter = "0x009688ff";

    private static final String IMG_PATH = "img/ls-test-1k.png";

    private static final int LONGSHADOW_LENGTH = 100;

    private static final String
            ANSI_GREEN = "\u001B[32m",
            ANSI_RESET = "\u001B[0m";


    @Override
    public void start(Stage stage) throws Exception {
        writer = new PrintWriter("out.txt", "UTF-8");

        InputStream is = new FileInputStream(new File(IMG_PATH));
        ImageView imageView = new ImageView(new Image(is));
        Image image = imageView.getImage();

        StackPane root = new StackPane();
        Scene scene = new Scene(root, image.getWidth(), image.getHeight(), Paint.valueOf
                ("#EEEEEE"));
        scene.addEventFilter(KeyEvent.KEY_PRESSED, evt -> {
            if (evt.getCode().equals(KeyCode.ESCAPE)) {
                stage.close();
            }
        });

        final Canvas canvas = new Canvas(image.getWidth(), image.getHeight());

        canvas.setOnMouseClicked((MouseEvent e) -> {
            Color color = image.getPixelReader().getColor((int) e.getX(), (int) e.getY());
            System.out.println(ANSI_GREEN + " -> " + color.toString() + ANSI_RESET);
            colorObjFilter = color.toString();

            try {
                processImage(root, canvas, image);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        });

        root.getChildren().addAll(imageView, canvas);
        stage.setScene(scene);
        stage.show();
    }


    private void processImage(StackPane root, Canvas canvas, Image image) throws IOException {
        long delta = System.currentTimeMillis();

        int width = (int) image.getWidth();
        int height = (int) image.getHeight();

        GraphicsContext gc = canvas.getGraphicsContext2D();
        System.out.println("width: " + width + "\theight: " + height);

        BufferedImage bufferedImage = ImageIO.read(new File(IMG_PATH));

        // keep threshold small to get clean paths to draw
        edgeDetection(gc, image, 0.00000001d);

        writer.close();
        Label label = new Label();
        root.setAlignment(Pos.BOTTOM_LEFT);
        root.setOnMouseMoved(event -> label.setText(event.getX() + "|" + event.getY()
                + "|" + bufferedImage.getRGB((int) event.getX(), (int) event.getY())));
        root.getChildren().addAll(label);
        System.out.println("took: " + (System.currentTimeMillis() - delta) + " ms");

    }


    public void edgeDetection(GraphicsContext gc, Image image, double threshold) {
        Color topPxl, lowerPxl;
        double topIntensity, lowerIntensity;
        PixelWriter pw = gc.getPixelWriter();

        for (int y = 0; y < image.getHeight() - 1; y++) {
            for (int x = 1; x < image.getWidth(); x++) {

                topPxl = image.getPixelReader().getColor(x, y);
                lowerPxl = image.getPixelReader().getColor(x - 1, y + 1);

                topIntensity = (topPxl.getRed() + topPxl.getGreen() + topPxl.getBlue()) / 3;
                lowerIntensity = (lowerPxl.getRed() + lowerPxl.getGreen() + lowerPxl.getBlue()) / 3;

                if (Math.abs(topIntensity - lowerIntensity) > threshold) {
                    int y2 = y;
                    for (int x2 = x; x2 < x + LONGSHADOW_LENGTH; x2++) {
                        y2++;
                        try {
                            Color color = image.getPixelReader().getColor(x2, y2);
                            // colorObjFilter protects the purple letter being manipulated
                            if (!color.toString().toLowerCase()
                                    .contains(colorObjFilter.toLowerCase())) {
                                pw.setColor(x2, y2, Color.color(.7f, .7f, .7f, .9f));
                            }
                        } catch (Exception e) {
                            System.out.println("Error: " + e.getMessage());
                        }
                    }
                }
            }
        }
    }


    public static void main(String[] args) {
        launch(args);
    }
}

最佳答案

我不知道为什么你的原始程序有一些渲染伪影。

这里有一个替代解决方案,这是极其暴力的,因为它只是生成一个阴影图像,并以不同的偏移量一遍又一遍地渲染,最终得到一个长阴影。演示了几种生成shadowImage的方法,其中之一是 ColorAdjust影响原始图像,另一个是使用 PixelWriter 生成阴影图像。 。

您最初的解决方案是使用 PixelWriter 来处理所有事情,并采用适当的阴影生成算法,这种解决方案更加优雅(如果您可以让它工作;-)。

shadow

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class ShadowSpray extends Application {
    private static final double W = 400;
    private static final double H = 400;
    private static final int SHADOW_LENGTH = 100;

    private static final double IMG_X = 20;
    private static final double IMG_Y = 20;

    private static final int FONT_SIZE = 200;

    private static final double SHADOW_SLOPE_FACTOR = 1.5;

    Color SHADOW_COLOR = Color.GRAY.brighter();

    @Override
    public void start(Stage stage) {
        Image image = getImage();

        Canvas canvas = new Canvas(W, H);
        GraphicsContext gc = canvas.getGraphicsContext2D();
//        drawWithShadowUsingStencil(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH, SHADOW_COLOR);
        drawWithShadowUsingColorAdjust(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH);

        stage.setScene(new Scene(new Group(canvas)));
        stage.show();
    }

    private void drawWithShadowUsingColorAdjust(GraphicsContext gc, Image image, double x, double y, int shadowLength) {
        // here the color adjust for the shadow is based upon the intensity of the input image color
        // which is a weird way to calculate a shadow color, but does come out nicely
        // because it appropriately handles antialiased input images.
        ColorAdjust monochrome = new ColorAdjust();
        monochrome.setBrightness(+0.5);
        monochrome.setSaturation(-1.0);

        gc.setEffect(monochrome);

        for (int offset = shadowLength; offset > 0; --offset) {
            gc.drawImage(image, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
        }

        gc.setEffect(null);

        gc.drawImage(image, x, y);
    }

    private void drawWithShadowUsingStencil(GraphicsContext gc, Image image, double x, double y, int shadowLength, Color shadowColor) {
        Image shadow = createShadowImage(image, shadowColor);

        for (int offset = shadowLength; offset > 0; --offset) {
            gc.drawImage(shadow, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
        }

        gc.drawImage(image, x, y);
    }


    private Image createShadowImage(Image image, Color shadowColor) {
        WritableImage shadow = new WritableImage(image.getPixelReader(), (int) image.getWidth(), (int) image.getHeight());
        PixelReader reader = shadow.getPixelReader();
        PixelWriter writer = shadow.getPixelWriter();
        for (int ix = 0; ix < image.getWidth(); ix++) {
            for (int iy = 0; iy < image.getHeight(); iy++) {
                int argb = reader.getArgb(ix, iy);
                int a = (argb >> 24) & 0xFF;
                int r = (argb >> 16) & 0xFF;
                int g = (argb >>  8) & 0xFF;
                int b =  argb        & 0xFF;

                // because we use a binary choice, we lose anti-alising info in the shadow so it looks a bit jaggy.
                Color fill = (r > 0 || g > 0 || b > 0) ? shadowColor : Color.TRANSPARENT;

                writer.setColor(ix, iy, fill);
            }
        }
        return shadow;
    }

    private Image getImage() {
        Label label = new Label("a");
        label.setStyle("-fx-text-fill: forestgreen; -fx-background-color: transparent; -fx-font-size: " + FONT_SIZE + "px;");
        Scene scene = new Scene(label, Color.TRANSPARENT);
        SnapshotParameters snapshotParameters = new SnapshotParameters();
        snapshotParameters.setFill(Color.TRANSPARENT);
        return label.snapshot(snapshotParameters, null);
    }

    public static void main(String[] args) {
        launch();
    }
}

JavaFX 内置有一个 DropShadow效果,这几乎就是你想要的,特别是当你将扩展设置为 1,半径设置为 0 时,但是,它只是生成单个偏移阴影图像,而不是长阴影效果。


使用一些替代文本和较短的“长阴影”:

Cat

关于JavaFX 渲染/图像处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42893202/

相关文章:

java - java中增强for循环的最后一次迭代

java - Micronaut JSON 序列化 - 使用 getter 而不是字段

android - 如何检查所选文件是图像?

java - 导出 (Eclipse) 可执行 jar 文件中的 UnsatisfiedLinkError

java - 替代 Fedora 上的机器人类

JavaFX 时间线与 ScheduledExecutorService

java - 检查struts2中的属性值

java - java.io.IOException : "The filename, directory name, or volume label syntax is incorrect" 的可能原因是什么

Swift - 裁剪允许编辑*外部*的图像

java - 最快的高斯模糊实现