【问题标题】:Can't modify little by little an image : thread is gets stuck不能一点一点地修改图像:线程卡住了
【发布时间】:2016-08-11 15:43:58
【问题描述】:

我有一张图片,我想反转它。我写了算法,效果很好。 但是我想一点一点地显示反转(显示第一列的像素,然后显示第二列的像素,等等),因为它更好!

所以我使用ServiceTask 类将包含此算法的类(GraphicEngine) 设为线程。后者将由另一个类 (Gui) 启动,即 GUI。在 JavaFX 中这样进行是很常见的。

问题是反转的显示卡在图像的开头或中间……这是随机的。我不知道为什么。

这里是源代码:

Launcher 类:

import javafx.application.Application;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Launcher extends Application {

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

    public void start(Stage stage) {
        stage.setWidth(700);
        stage.setHeight(700);
        stage.setMaximized(true);

        Gui gui = new Gui(stage, new BorderPane(), 700, 500);
        gui.fill();

        stage.setScene(gui);
        stage.show();
    }

}

在 Gui.java 中(扩展场景):

    this.button_reverse_image.setOnAction(button -> {
        this.graphic_engine.restart();
    });

在 GraphicEngine.java 中:

public class GraphicEngine extends Service<Void> {
    private WritableImage writable_image;
    private PixelReader writable_pixel_reader;
    private PixelWriter writable_pixel_writer;

    public Task<Void> createTask() {
        return new Task<Void>() {

            protected Void call() {
                double r, g, b;
                for(int x = 0; x < writable_image.getWidth(); x++) {
                    for(int y = 0; y < writable_image.getHeight(); y++) {
                        r = writable_pixel_reader.getColor(x, y).getRed()*255;
                        g = writable_pixel_reader.getColor(x, y).getGreen()*255;
                        b = writable_pixel_reader.getColor(x, y).getBlue()*255;
                        writable_pixel_writer.setColor(x, y, Color.rgb((int) (255 - r), (int) (255 - g), (int) (255 - b)));
                    }
                }
                return null;
            }
        };
    }

【问题讨论】:

  • 您是否在单独的线程上运行任务?请提供执行任务的代码(大概是GraphicEngine#restart?)
  • restart 在类Gui 中被调用,它扩展了Scene
  • 我添加了第三个(也是最后一个)类的源代码:Launcher
  • @sillyfly Service 通过默认执行程序管理线程。
  • @James_D - 啊,对,我错过了 GenericEngine 扩展 Service 的事实。

标签: javafx task


【解决方案1】:

您无法从后台线程修改 UI。相反,您应该使用动画(因为为新图像制作动画实际上就是您正在做的事情)。

例如

private void progressivelyWriteImage(PixelReader source, WritableImage target) {

    Duration animationDuration = Duration.seconds(1);

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

    PixelWriter pixelWriter = target.getPixelWriter();

    IntegerProperty columnProgression = new SimpleIntegerProperty(0);
    columnProgression.addListener((obs, oldX, newX) -> {
        int progWidth = newX.intValue() - oldX.intValue();
        int[] pixels = new int[progWidth*height];
        source.getPixels(oldX.intValue(), 0, progWidth, height,
            PixelFormat.getIntArgbInstance(),
            pixels, 0, progWidth);
        int[] newPixels = new int[pixels.length];
        for (int i = 0; i < pixels.length ; i++) {
            int a = pixels[i] & 0xFF000000 ;
            int rgb = 0xFFFFFF - (pixels[i] & 0xFFFFFF) ;
            newPixels[i] = a | rgb ;
        }
        pixelWriter.setPixels(oldX.intValue(), 0, progWidth, height,
            PixelFormat.getIntArgbInstance(),
            newPixels, 0, progWidth);
    });

    Timeline timeline = new Timeline(new KeyFrame(animationDuration,
        new KeyValue(columnProgression, width)));
    timeline.play();
}

这是一个 SSCCE:

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.util.Duration;

public class GraphicEngine() {

    public negateImage(PixelReader source, WritableImage target) {

        Duration animationDuration = Duration.seconds(1);

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

        PixelWriter pixelWriter = target.getPixelWriter();

        IntegerProperty columnProgression = new SimpleIntegerProperty(0);
        columnProgression.addListener((obs, oldX, newX) -> {
            int progWidth = newX.intValue() - oldX.intValue();
            int[] pixels = new int[progWidth*height];
            source.getPixels(oldX.intValue(), 0, progWidth, height,
                PixelFormat.getIntArgbInstance(),
                pixels, 0, progWidth);
            int[] newPixels = new int[pixels.length];
            for (int i = 0; i < pixels.length ; i++) {
                int a = pixels[i] & 0xFF000000 ;
                int rgb = 0xFFFFFF - (pixels[i] & 0xFFFFFF) ;
                newPixels[i] = a | rgb ;
            }
            pixelWriter.setPixels(oldX.intValue(), 0, progWidth, height,
                PixelFormat.getIntArgbInstance(),
                newPixels, 0, progWidth);
        });

        Timeline timeline = new Timeline(new KeyFrame(animationDuration,
            new KeyValue(columnProgression, width)));
        timeline.play();
    }
}

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ImageModifyDemo extends Application {

    @Override
    public void start(Stage primaryStage) {

        GraphicEngine engine = new GraphicEngine();
        Image initialImage = createImage();

        int width = (int) initialImage.getWidth();
        int height = (int) initialImage.getHeight();
        WritableImage target = new WritableImage(width, height);

        Button demoChange = new Button("Run demo");
        demoChange.setOnAction(e -> {
            target.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), new int[width], 0, 0);
            engine.negateImage(initialImage.getPixelReader(), target);
        });

        VBox root = new VBox(10, new ImageView(initialImage), new ImageView(target), demoChange);
        root.setPadding(new Insets(10));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }


    private Image createImage() {
        Label label = new Label("Hello world");
        label.setStyle("-fx-background-color: -fx-background; -fx-background: salmon; -fx-font-size:72;");
        new Scene(label);
        return label.snapshot(null, null);

    }

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

【讨论】:

  • 非常感谢,但这里的问题是我真的必须在“A”类中编写算法,并在另一个“B”类中进行显示。事实上,我必须将它们分开。在您的最后一个解决方案中,progressivelyWriteImage 修改并显示修改。是不是不能把动画存储在ImageUpdate之类的?
  • 方法所在的类无关紧要。重要的是您在 FX 应用程序线程上进行 UI 更新。如果您希望以这种方式组织代码,请将 progressivelyWriteIage 方法移至新类。
  • 我可以保留我的 GraphicEngine,它扩展了服务和我的任务(你使用线程代替)?我可以将int[] newPixels = new int[height] ; 放在第一行之前吗(我必须将其更改为:new int[height*width])?
  • 事实上我认为我不需要一个 int[] 缓冲区,因为我想逐个像素(而不是逐列)绘制我的图像,它是否简化了你的代码?
  • Service 类运行它在后台线程中创建的Tasks。再次(第三次左右)您不得从后台线程修改 UI。所以你可以使用另一个类,但不要使用Service。完全没有理由为此使用后台线程:您应该使用动画来做您想做的事情。当然,如果您一次更新一个像素,则使用int 而不是int[]:但这些细节与您看到的问题无关,(据我所知)是由通过从后台线程更新 UI。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
  • 2015-01-28
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 2014-11-26
  • 1970-01-01
相关资源
最近更新 更多