java 如何保存一个图像快照的一个BMP格式?

pgccezyw  于 5个月前  发布在  Java
关注(0)|答案(2)|浏览(89)

我已经成功地在同一个窗格上绘制了两个图形(条形图和折线图)。
我试图实现一个保存按钮,当点击它时,将结果图像(带轴)写入我保存的bmp图像。
代码运行,我得到一个肯定的警告,并创建了一个图像文件。然而,生成的图像文件是空的(0字节)。

@FXML // fx:id="graph"
    private Pane graph; // Value injected by FXMLLoader

@FXML // fx:id="saveButton"
    private Button saveButton; // Value injected by FXMLLoader

// ...

@FXML
    void clickSave(ActionEvent event) {
        Stage yourStage = (Stage) saveButton.getScene().getWindow();

        FileChooser fileChooser = new FileChooser();
        fileChooser.setInitialDirectory(new File("Path\\With\\Spaces"));
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("BMP Files", "*.bmp"));

        // Show save dialog
        File file = fileChooser.showSaveDialog(yourStage);

        if (file != null) {
            if (!file.exists()) {
                try {
                    Files.createFile(file.toPath());
                } catch (IOException e) {
                    e.printStackTrace(); // Handle the exception
                }
            }

            WritableImage writableImage = graph.snapshot(new SnapshotParameters(), null);
            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(writableImage, null);

            try {
                ImageIO.write(bufferedImage, "BMP", file);

                // Inform the user about the successful save
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("File Saved");
                alert.setHeaderText(null);
                alert.setContentText("The file has been saved successfully.");
                alert.showAndWait();
            } catch (IOException e) {
                e.printStackTrace();

                // Inform the user about the error
                Alert alert = new Alert(Alert.AlertType.ERROR);
                alert.setTitle("Error");
                alert.setHeaderText(null);
                alert.setContentText("An error occurred while saving the file.");
                alert.showAndWait();
            }
        }
    }

字符串
编辑:遵循@James_D的评论建议,我将代码更改为以下内容,但问题仍然存在。

@FXML
    void clickSave(ActionEvent event) {
        Stage stage = (Stage) saveButton.getScene().getWindow();

        FileChooser fileChooser = new FileChooser();
        fileChooser.setInitialDirectory(new File("Path\\With\\Spaces"));
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("BMP Files", "*.bmp"));

        // Show save dialog
        File file = fileChooser.showSaveDialog(stage);

        if (file != null) {
            WritableImage writableImage = graph.snapshot(new SnapshotParameters(), null);
            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(writableImage, null);

            try {
                ImageIO.write(bufferedImage, "BMP", file);

                if (!file.exists()) {
                    Files.createFile(file.toPath());
                }

                // Inform the user about the successful save
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("File Saved");
                alert.setHeaderText(null);
                alert.setContentText("The file has been saved successfully.");
                alert.showAndWait();
            } catch (IOException e) {
                e.printStackTrace();

                // Inform the user about the error
                Alert alert = new Alert(Alert.AlertType.ERROR);
                alert.setTitle("Error");
                alert.setHeaderText(null);
                alert.setContentText("An error occurred while saving the file.");
                alert.showAndWait();
            }
        }
    }

4sup72z8

4sup72z81#

非常感谢@Slaw和this answer
SwingFXUtils函数返回TYPE_INT_ARGB_PRE类型的图像。只有TYPE_INT_RGB类型的图像可以写入.bmp文件,否则ImageIO.write(bufferedImage, "BMP", file);返回false。因此,必须完成从TYPE_INT_ARGB_PRETYPE_INT_RGB的适当转换。下面的块显示了更新后的代码,其中现在包括转换。

WritableImage writableImage = graph.snapshot(new SnapshotParameters(), null);
BufferedImage preBufferedImage = SwingFXUtils.fromFXImage(writableImage, null);

BufferedImage bufferedImage = new BufferedImage(
        preBufferedImage.getWidth(),
        preBufferedImage.getHeight(),
        BufferedImage.TYPE_INT_RGB
);
bufferedImage.createGraphics().drawImage(
        preBufferedImage,
        0,
        0,
        java.awt.Color.WHITE,
        null
);

try {
    Boolean __ = ImageIO.write(bufferedImage, "BMP", file);

    if (!file.exists()) {
        Files.createFile(file.toPath());
    }
    ...
}

字符串

vwoqyblh

vwoqyblh2#

问题

ImageIO::write方法将返回false:
如果没有找到适当写入器
在这种情况下不会抛出异常。
根据ImageIO.write bmp does not work问答,内置的BMP图像写入器要求图像的类型为TYPE_INT_RGB。如果BufferedImage不具有该类型,则对write的调用将无法找到“适当的写入器”,并将返回false,这意味着没有图像写入文件。
看看SwingFXUtils::fromFXImage的实现,返回图像的类型取决于源图像的格式以及您是否传入了自己的BufferedImage。从一些经验来看,通过拍摄节点快照创建的JavaFX图像的类型似乎是TYPE_INT_ARGB_PRE。不幸的是,这是错误的类型,因此在你的例子中没有图像被写入文件。但是因为你没有检查返回值,你错误地向用户报告成功。

解决方案

要解决这个问题,您需要强制BufferedImage具有TYPE_INT_RGB类型。这里有三种不同的方法。

预先创建BufferedImage所需类型

如果你能保证JavaFX Image是不透明的,我不确定关于快照,那么可以说最简单的方法是传递你自己预先创建的BufferedImage。例如:

public static BufferedImage toBufferedImageRGB(Image fxImage) {
  if (fxImage.getPixelReader() == null) {
    throw new IllegalArgumentException("fxImage is not readable");
  }

  int width = (int) fxImage.getWidth();
  int height = (int) fxImage.getHeight();
  var target = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

  var awtImage = SwingFXUtils.fromFXImage(fxImage, target);
  if (awtImage.getType() != BufferedImage.TYPE_INT_RGB) {
    throw new RuntimeException("fxImage could not be converted to TYPE_INT_RGB");
  }
  return awtImage;
}

字符串

手动复制像素数据

您可以手动将像素从PixelReader复制到BufferedImage。例如:

public static BufferedImage toBufferedImageRGB(Image fxImage) {
  var reader = fxImage.getPixelReader();
  if (reader == null) {
    throw new IllegalArgumentException("fxImage is not readable");
  }

  int w = (int) fxImage.getWidth();
  int h = (int) fxImage.getHeight();

  var awtImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
  for (int x = 0; x < w; x++) {
    for (int y = 0; y < h; y++) {
      awtImage.setRGB(x, y, reader.getArgb(x, y));
    }
  }

  return awtImage;
}


应该可以将像素读取为ARGB,因为在这种情况下,当写入BufferedImage时,alpha通道只是“丢失”。

BufferedImage复制到新的BufferedImage中,复制类型为BufferedImage

这种方法基于this answerImageIO.write bmp does not work

public static BufferedImage toBufferedImageRGB(Image fxImage) {
  if (fxImage.getPixelReader() == null) {
    throw new IllegalArgumentException("fxImage is not readable");
  }

  var awtImage = SwingFXUtils.fromFXImage(fxImage, null);
  if (awtImage.getType() != BufferedImage.TYPE_INT_RGB) {
    int width = awtImage.getWidth();
    int height = awtImage.getHeight();
    var copy = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

    var graphics = copy.createGraphics();
    graphics.drawImage(awtImage, 0, 0, java.awt.Color.WHITE, null);
    graphics.dispose();
    awtImage = copy;
  }
  return awtImage;
}

注意事项

  • 如果该文件不存在,则没有理由手动创建该文件。该文件将作为写出图像过程的一部分创建。如果该文件已经存在,则它将被覆盖。
  • 您不应该在 *JavaFX应用程序线程 * 上执行I/O工作。获取快照后的所有工作,包括将其转换为BufferedImage,都可以并且应该在后台线程上完成。然后,无论正常还是异常,都可以在 *JavaFX应用程序线程 * 上对后台工作完成做出React(这是您显示警报、重新启用控件等的地方)。

有关更多信息,请参阅JavaFX中的并发和javafx.concurrent包。javafx.concurrent.Task类特别适合这种情况。

  • 来自Jewelsea的评论:

这不是你在这里的主要问题 [...],但要注意的是,当在图表上调用快照时,通常你想将图表中的animate设置为false,以便快照将捕获图表的最终状态,而不是在图表更改动画完成之前的状态。
还有:
snapshot文档中提到:“* 当拍摄正在动画化的场景的快照时,无论是由应用程序显式地还是隐式地(例如图表动画),快照都将基于拍摄快照时场景图形的状态进行渲染,并且不会反映任何后续的动画更改 *”。

相关问题