当前位置: 首页 > 知识库问答 >
问题:

尽快从PGM或TIFF创建JavaFX图像

南宫正阳
2023-03-14

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

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

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

有什么想法吗?

编辑#1

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

感谢@all的评论。

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

编辑#2

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

目前,我的后端有一个可写图像,应该一步一步地用数据填充。此图像实例设置为最终绑定到图像视图的对象属性。由于可写图像(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]

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

共有2个答案

楚瑞
2023-03-14

下载jai\u imageio。jar并将其包含在项目中。将tiff图像转换为fx可读图像的代码如下:

String pathToImage = "D:\\ABC.TIF";
ImageInputStream is;
try {
is = ImageIO.createImageInputStream(new File(pathToImage));  //read tiff using imageIO (JAI component)
if (is == null || is.length() == 0) {
    System.out.println("Image is null");
}

Iterator<ImageReader> iterator = ImageIO.getImageReaders(is);
if (iterator == null || !iterator.hasNext()) {
    throw new IOException("Image file format not supported by ImageIO: " + pathToImage);
}
ImageReader reader = (ImageReader) iterator.next();
reader.setInput(is);
int nbPages = reader.getNumImages(true);
BufferedImage bf = reader.read(0);   //1st page of tiff file
BufferedImage bf1 = reader.read(1);  //2nd page of tiff file
WritableImage wr = null;
WritableImage wr1 = null;
if (bf != null) {
    wr= SwingFXUtils.toFXImage(bf, null);   //convert bufferedImage (awt) into Writable Image(fx)
}
if (bf != null) {
    wr1= SwingFXUtils.toFXImage(bf1, null);  //convert bufferedImage (awt) into Writable Image(fx)
}
img_view1.setImage(wr);
img_view2.setImage(wr1);

} catch (FileNotFoundException ex) {
        Logger.getLogger(Image_WindowController.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
        Logger.getLogger(Image_WindowController.class.getName()).log(Level.SEVERE, null, ex);
}

这是我关于堆栈溢出的第一个答案。希望有帮助!

狄宇
2023-03-14

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

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

未压缩的TIFF应该在大致相同的时间内可读,尽管TIFF IFD结构比非常简单的PGM标头更复杂。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);
        }
    }
}

我应该补充一下,我使用JavaFX2.2在Java 7上编写、编译和运行了上述代码。

更新:使用预定义的PixelFormat,我可以使用PixelWriter。设置像素,从而将相同640x480样本图像的读取时间进一步减少到45-60 ms。下面是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;
}
 类似资料:
  • 我正在编写一个图像处理应用程序,使用javafx作为gui,我想在屏幕上显示处理过的图片。我的程序将图像处理为三维双数组,如double[3][1080][1920],因此我必须减少图像的颜色空间,并将双数组转换为byte或(如果可能)shorts,因为javafx的ImageView不支持浮点值或每个RGB组件的64位。我发现,Imageview只能从文件或inputstream加载数据,因此我

  • 停留在基础上。我在设置图像路径时遇到了一些语法问题。当我尝试创建一个图像并给它图像路径时,它总是抛出一些关于路径的一些异常。我已经评论了一些我已经尝试过的路径组合。你能告诉我我做错了什么吗?谢谢你。 封装JOPOFX; 这是打印出来的内容:错误while creating image java.lang.IllegalArgumentException:无效URL:在javafx.scene.im

  • 我知道如何从图弗创建PDF。我的问题是:itext如何只嵌入原始的TIFF而不修改它们? 我使用文档添加(其中 img 是 TIFF)来创建 PDF。但是,TIFF被修改为较小的尺寸。在这种情况下,我的原始未压缩的黑白 TIFF 文件大小为 2.8 MB,被压缩为 CCITT 组 4 TIFF。 文本有没有办法不修改TIFF?

  • 我有一个pgm文件,格式如下 请帮帮我。。我不知道是什么问题。

  • 问题内容: 我正在寻找一个Java库,该库可以获取图像(PNG)并创建PDF。或直接从已绘制的Java面板中创建PDF。 问题答案: 您可以使用Gnostice PDFOne for Java(http://www.gnostice.com/PDFOne_Java.asp)实现此目的。 在下面的代码片段中找到可从PNG图像创建PDF文档的代码。 要从JPanel创建BufferedImage,可以

  • 我正在寻找一个Java库,将可以采取一个图像(PNG)和创建一个PDF。或者直接从已绘制的java面板创建PDF。