Это правда, что вы не можете использовать PixelWriter в потоке, отличном от потока приложения JavaFX.Тем не менее, ваша задача может поместить данные пикселя в объект значения, отличный от JavaFX, например IntBuffer , который поток приложения затем может передать setPixels .Пример программы:
import java.nio.IntBuffer;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
public class PixelGeneratorTest
extends Application {
public static final int WIDTH = Integer.getInteger("width", 500);
public static final int HEIGHT = Integer.getInteger("height", 500);
public class PixelGenerator
extends Task<IntBuffer> {
private final int width;
private final int height;
public PixelGenerator(int width,
int height) {
this.width = width;
this.height = height;
}
@Override
public IntBuffer call() {
IntBuffer buffer = IntBuffer.allocate(width * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color pixel = Color.hsb(
y * 360.0 / HEIGHT, 1, (double) x / WIDTH);
int argb = 0xff000000 |
((int) (pixel.getRed() * 255) << 16) |
((int) (pixel.getGreen() * 255) << 8) |
(int) (pixel.getBlue() * 255);
buffer.put(argb);
}
}
buffer.flip();
return buffer;
}
}
@Override
public void start(Stage stage) {
Canvas canvas = new Canvas(WIDTH, HEIGHT);
stage.setTitle("PixelGenerator Test");
stage.setScene(new Scene(new BorderPane(canvas)));
stage.show();
GraphicsContext gc = canvas.getGraphicsContext2D();
PixelWriter pw = gc.getPixelWriter();
PixelGenerator generator = new PixelGenerator(WIDTH, HEIGHT);
generator.valueProperty().addListener((o, oldValue, pixels) ->
pw.setPixels(0, 0, WIDTH, HEIGHT,
PixelFormat.getIntArgbInstance(), pixels, WIDTH));
Thread th = new Thread(generator);
th.setDaemon(true);
th.start();
}
}
Если вы ожидаете, что ваше изображение будет очень большим и, следовательно, нецелесообразно хранить его в памяти сразу, вы можете написать задачу, чтобы принимать аргументы конструктора, которые позволяют ему генерировать только частьизображения, а затем создать несколько задач для обработки генерации пикселей по частям.Другим вариантом является наличие одной Задачи, которая многократно вызывает updateValue , но затем вам нужно будет создать класс пользовательских значений, который будет содержать как буфер, так и прямоугольную область изображения, к которой этот буфер должен быть применен.
Обновление:
Вы уточнили, что хотите прогрессивную визуализацию изображения.Задача не будет работать для быстрых обновлений, поскольку JavaFX может объединять изменения в значении Задачи.Итак, вернемся к основам: создайте Runnable, который вызывает setPixels в вызове Platform.runLater , чтобы обеспечить правильное использование потока:
import java.nio.IntBuffer;
import java.util.Objects;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
public class PixelGeneratorTest2
extends Application {
public static final int WIDTH = Integer.getInteger("width", 500);
public static final int HEIGHT = Integer.getInteger("height", 500);
private static final PixelFormat<IntBuffer> pixelFormat =
PixelFormat.getIntArgbInstance();
public class PixelGenerator
implements Runnable {
private final int width;
private final int height;
private final PixelWriter writer;
public PixelGenerator(int width,
int height,
PixelWriter pw) {
this.width = width;
this.height = height;
this.writer = Objects.requireNonNull(pw, "Writer cannot be null");
}
@Override
public void run() {
int blockHeight = 4;
IntBuffer buffer = IntBuffer.allocate(width * blockHeight);
try {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color pixel = Color.hsb(
y * 360.0 / HEIGHT, 1, (double) x / WIDTH);
int argb = 0xff000000 |
((int) (pixel.getRed() * 255) << 16) |
((int) (pixel.getGreen() * 255) << 8) |
(int) (pixel.getBlue() * 255);
buffer.put(argb);
}
if (y % blockHeight == blockHeight - 1 || y == height - 1) {
buffer.flip();
int regionY = y - y % blockHeight;
int regionHeight =
Math.min(blockHeight, height - regionY);
Platform.runLater(() ->
writer.setPixels(0, regionY, width, regionHeight,
pixelFormat, buffer, width));
buffer.clear();
}
// Pretend pixel calculation was CPU-intensive.
Thread.sleep(25);
}
} catch (InterruptedException e) {
System.err.println("Interrupted, exiting.");
}
}
}
@Override
public void start(Stage stage) {
Canvas canvas = new Canvas(WIDTH, HEIGHT);
stage.setTitle("PixelGenerator Test");
stage.setScene(new Scene(new BorderPane(canvas)));
stage.show();
GraphicsContext gc = canvas.getGraphicsContext2D();
PixelWriter pw = gc.getPixelWriter();
PixelGenerator generator = new PixelGenerator(WIDTH, HEIGHT, pw);
Thread th = new Thread(generator);
th.setDaemon(true);
th.start();
}
}
Вы могли бы вызывать платформу.runLater для каждого отдельного пикселя, но я подозреваю, что это приведет к перегрузке потока приложения JavaFX.