gpt4 book ai didi

java - 尽可能快地从 PGM 或 TIFF 创建 JavaFX 图像

转载 作者:行者123 更新时间:2023-11-30 09:07:10 27 4
gpt4 key购买 nike

我正在使用 java 从扫描仪设备捕获图像。输入格式为 PGM 或 TIFF。我必须在用户界面中显示实时结果。其实我用的是ImageJ将源输入流读取为 tiff,因为 ImageJ 还可以处理不完整的流。之后,ImagePlus 对象被转换为 BufferedImage,最后转换为 JavaFX Image

ImagePlus imagePlus = new Opener().openTiff(inputStream, "");
BufferedImage bufferedImage = imagePlus.getBufferedImage();
Image image = SwingFXUtils.toFXImage(bufferedImage, null);

这很慢。我需要一种更快的方法来从 PGM 或 TIFF 流创建 JavaFX Image。 JavaFX 似乎实际上不支持这种格式,我也没有找到有用的库。

有什么想法吗?

编辑 #1

我决定分两步优化图像捕捉。起初我需要在 ui 中更新图像时更好的状态控制。这实际上已经完成并且工作正常。现在,当转换线程繁忙时,更新请求被丢弃。第二步是使用自行实现的 pnm 阅读器(基于建议的实现)并逐步更新模型中的图像……直到扫描过程完成。这应该减少从设备加载图像时所需的资源。我需要更改架构的某些部分才能实现这一目标。

感谢@大家的评论。

顺便说一句:java 8 lambdas 很棒:)

编辑 #2

我的计划行不通,因为 JavaFX 的线程测试:(

目前我的后端有一个 WritableImage 应该用数据逐步填充。此图像实例设置为最终绑定(bind)到 ImageViewObjectProperty。由于 WritableImage 已连接到 ImageView,因此无法使用 PixelWriter 向其填充数据。这会导致异常。

java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-2-thread-1
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:210) ~[jfxrt.jar:na]
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:393) ~[jfxrt.jar:na]
at javafx.scene.Scene.addToDirtyList(Scene.java:529) ~[jfxrt.jar:na]
at javafx.scene.Node.addToSceneDirtyList(Node.java:417) ~[jfxrt.jar:na]
at javafx.scene.Node.impl_markDirty(Node.java:408) ~[jfxrt.jar:na]
at javafx.scene.Node.transformedBoundsChanged(Node.java:3789) ~[jfxrt.jar:na]
at javafx.scene.Node.impl_geomChanged(Node.java:3753) ~[jfxrt.jar:na]
at javafx.scene.image.ImageView.access$700(ImageView.java:141) ~[jfxrt.jar:na]
at javafx.scene.image.ImageView$3.invalidated(ImageView.java:285) ~[jfxrt.jar:na]
at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83) ~[jfxrt.jar:na]
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:135) ~[jfxrt.jar:na]
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80) ~[jfxrt.jar:na]
at javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:74) ~[jfxrt.jar:na]
at javafx.scene.image.Image$ObjectPropertyImpl.fireValueChangedEvent(Image.java:568) ~[jfxrt.jar:na]
at javafx.scene.image.Image.pixelsDirty(Image.java:542) ~[jfxrt.jar:na]
at javafx.scene.image.WritableImage$2.setArgb(WritableImage.java:170) ~[jfxrt.jar:na]
at javafx.scene.image.WritableImage$2.setColor(WritableImage.java:179) ~[jfxrt.jar:na]

我的解决方法是创建图像的副本,但我不喜欢这种解决方案。也许可以阻止自动更改通知并手动执行此操作?

最佳答案

作为一个实验,为了学习一些 JavaFX,我决定亲眼看看实现我在上面评论中建议的内容有多难......:-)

PGM 读数改编 self 的 PNM ImageIO 插件,它似乎工作正常。据报道,我的 640x480 样本图像的读取时间约为 70-90 毫秒(如果有,请随时向我发送更多样本!)。

虽然 TIFF IFD 结构比非常简单的 PGM header 解析起来更复杂,但未压缩的 TIFF 应该可以在大致相同的时间内读取。 TIFF 压缩会增加一些解压缩开销,具体取决于压缩类型和设置。

import java.io.DataInputStream;
import java.io.IOException;

import javax.imageio.IIOException;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class PGMTest extends Application {

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

@Override
public void start(Stage primaryStage) throws IOException {
Label root = new Label();
Image image;

long start = System.currentTimeMillis();
DataInputStream input = new DataInputStream(getClass().getResourceAsStream("/house.l.pgm"));
try {
image = readImage(input);
} finally {
input.close();
}
System.out.printf("Read image (%f x %f) in: %d ms\n", image.getWidth(), image.getHeight(), System.currentTimeMillis() - start);

root.setGraphic(new ImageView(image));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

private Image readImage(final DataInputStream input) throws IOException {
// First parse PGM header
PNMHeader header = PNMHeader.parse(input);

WritableImage image = new WritableImage(header.getWidth(), header.getHeight());
PixelWriter pixelWriter = image.getPixelWriter();

int maxSample = header.getMaxSample(); // Needed for normalization

// PixelFormat<ByteBuffer> gray = PixelFormat.createByteIndexedInstance(createGrayColorMap());

byte[] rowBuffer = new byte[header.getWidth()];
for (int y = 0; y < header.getHeight(); y++) {
input.readFully(rowBuffer); // Read one row

// normalize(rowBuffer, maxSample);
// pixelWriter.setPixels(0, y, width, 1, gray, rowBuffer, 0, width); // Gives weird NPE for me...

// As I can't get setPixels to work, we'll set pixels directly
// Performance is probably worse than setPixels, but it seems "ok"-ish
for (int x = 0; x < rowBuffer.length; x++) {
int gray = (rowBuffer[x] & 0xff) * 255 / maxSample; // Normalize [0...255]
pixelWriter.setArgb(x, y, 0xff000000 | gray << 16 | gray << 8 | gray);
}
}

return image;
}

private int[] createGrayColorMap() {
int[] colors = new int[256];
for (int i = 0; i < colors.length; i++) {
colors[i] = 0xff000000 | i << 16 | i << 8 | i;
}
return colors;
}

/**
* Simplified version of my PNMHeader parser
*/
private static class PNMHeader {
public static final int PGM = 'P' << 8 | '5';

private final int width;
private final int height;
private final int maxSample;

private PNMHeader(final int width, final int height, final int maxSample) {
this.width = width;
this.height = height;
this.maxSample = maxSample;
}

public int getWidth() {
return width;
}

public int getHeight() {
return height;
}

public int getMaxSample() {
return maxSample;
}

public static PNMHeader parse(final DataInputStream input) throws IOException {
short type = input.readShort();

if (type != PGM) {
throw new IIOException(String.format("Only PGM binay (P5) supported for now: %04x", type));
}

int width = 0;
int height = 0;
int maxSample = 0;

while (width == 0 || height == 0 || maxSample == 0) {
String line = input.readLine(); // For PGM I guess this is ok...

if (line == null) {
throw new IIOException("Unexpeced end of stream");
}

if (line.indexOf('#') >= 0) {
// Skip comment
continue;
}

line = line.trim();

if (!line.isEmpty()) {
// We have tokens...
String[] tokens = line.split("\\s");
for (String token : tokens) {
if (width == 0) {
width = Integer.parseInt(token);
} else if (height == 0) {
height = Integer.parseInt(token);
} else if (maxSample == 0) {
maxSample = Integer.parseInt(token);
} else {
throw new IIOException("Unknown PBM token: " + token);
}
}
}
}

return new PNMHeader(width, height, maxSample);
}
}
}

我应该补充一点,我在 Java 7 上使用 JavaFX 2.2 编写、编译和运行了上述代码。


更新:使用预定义的 PixelFormat 我能够使用 PixelWriter.setPixels,从而进一步将相同 640x480 示例图像的读取时间减少到 45-60 毫秒。这是 readImage 的新版本(代码在其他方面相同):

private Image readImage(final DataInputStream input) throws IOException {
// First parse PGM header
PNMHeader header = PNMHeader.parse(input);

int width = header.getWidth();
int height = header.getHeight();
WritableImage image = new WritableImage(width, height);
PixelWriter pixelWriter = image.getPixelWriter();

int maxSample = header.getMaxSample(); // Needed to normalize

PixelFormat<ByteBuffer> format = PixelFormat.getByteRgbInstance();

byte[] rowBuffer = new byte[width * 3]; // * 3 to hold RGB
for (int y = 0; y < height; y++) {
input.readFully(rowBuffer, 0, width); // Read one row

// Expand gray to RGB triplets
for (int i = width - 1; i > 0; i--) {
byte gray = (byte) ((rowBuffer[i] & 0xff) * 255 / maxSample); // Normalize [0...255];
rowBuffer[i * 3 ] = gray;
rowBuffer[i * 3 + 1] = gray;
rowBuffer[i * 3 + 2] = gray;
}

pixelWriter.setPixels(0, y, width, 1, format, rowBuffer, 0, width * 3);
}

return image;
}

关于java - 尽可能快地从 PGM 或 TIFF 创建 JavaFX 图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24106046/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com