使用JavaFX将图像放在PDF文件上

时间:2013-08-04 01:02:28

标签: java javafx-2 pdfbox

我已经使用来自Apache的Javafx和PDFbox将文本放入pdf文件中。现在我正在尝试将拍摄场景的屏幕截图放到pdf文档中。

    WritableImage snapshot = quotes.getScene().snapshot(null);
    PDDocument doc = null;
    PDPage page = null;
    PDXObjectImage ximg = null;
    BufferedImage bufferedImg = SwingFXUtils.fromFXImage(snapshot, null);

   try{
       doc = new PDDocument();
       page = new PDPage();


       doc.addPage(page);
       PDPageContentStream content = new PDPageContentStream(doc, page);


      /* ByteArrayOutputStream out = new ByteArrayOutputStream();
      InputStream in = new ByteArrayInputStream(out.toByteArray());*/

      ximg = new PDJpeg(doc, bufferedImg);
      //ximg = new PDJpeg(doc, in);
      content.drawImage(ximg, 100, 100);

      content.close();
      doc.save("PDFWithText.pdf");
      doc.close();
    } catch (Exception e){
    System.out.println(e);
    }

当我使用InputStream和OutputStream类时,文档已创建,但是当我打开它时,Adobe和其他程序会给我一个错误,说Acrobat无法正确显示页面。当我使用BufferedImage类而不是在上面的代码中注释掉时,文档很简单,没有创建,我不知道为什么。

这是Taking a screenshot of a scene or a portion of a scene in JavaFx 2.2的后续问题,但特定于pdf创建部分而非节点快照部分。

2 个答案:

答案 0 :(得分:4)

这不是一个真正的解决方案(因为提供的代码不起作用),而是我试图解决问题的文档

我花了一些时间试图解决这个问题而且无法做到。我已经为我的解决方案提供了示例代码,因为它提供了一个SSCCE和方法,可以帮助其他人尝试解决这个问题。

我尝试的两种方法是:

  1. 通过JavaFX方法SwingFXUtils.fromFXImage输出BufferedImage,并将其用作pdfbox api的输入。

    这种方法不起作用,因为SwingFXUtils创建的BufferedImage使用与pdfbox api所需的SampleModel不兼容的SampleModel进行编码。

  2. 通过JavaFX方法SwingFXUtils.fromFXImage从BufferedImage输出中创建Jpeg流(使用ImageIO),并将其用作pdfbox api的输入。

    这种方法不起作用。也许失败的原因是ImageIO从SwingFXUtils创建的BufferedImage创建了粉红色(即错误编码)的jpeg。这可能是ImageIO中的一个错误。另外,我可能会在使用pdfbox将结果图像添加到jpeg中时出现错误。

  3. 建议

    还有许多用于从JavaFX创建PDF文件的api。我建议尝试使用任何其他api来执行创建pdf文件的任务(谷歌搜索会显示它们),而不是继续解决从JavaFX输出的图像与pdfbox的集成问题。

    可执行示例代码

    import javafx.application.Application;
    import javafx.embed.swing.SwingFXUtils;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Group;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import org.apache.pdfbox.exceptions.COSVisitorException;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.pdmodel.PDPage;
    import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
    import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
    import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
    
    import javax.imageio.ImageIO;
    import javax.imageio.stream.ImageOutputStream;
    import java.awt.image.*;
    import java.io.*;
    import java.nio.file.Paths;
    import java.util.Arrays;
    
    // #### THIS CODE CURRENTLY DOES NOT FUNCTION CORRECTLY - SEE INLINE COMMENTS IN THE CODE TO UNDERSTAND WHY ####
    //
    // Demonstrates converting a JavaFX SceneGraph to a pdf (just as a bitmapped image, not as vector graphics).
    //   1. creating a snapshot of a JavaFX node.
    //   2. creating a pdf from the snapshot (using apache pdfbox http://pdfbox.apache.org/).
    //   3. saving the pdf to a file.
    //   4. opening the saved pdf in a web browser so the web browser can trigger showing
    //      the pdf in an appropriate pdf viewer (if the user has such a viewer installed).
    public class PdfWithImageCreator extends Application {
        // icon courtesy of http://www.aha-soft.com/ not for commercial use (free for non-commercial use).
        private static final String imageURL =
                "http://icons.iconarchive.com/icons/aha-soft/free-global-security/512/Global-Network-icon.png";
    
        private static final String PDF_PATH =
                Paths.get("exported.pdf").toAbsolutePath().toString();
    
        @Override public void start(Stage stage) {
            VBox layout = new VBox(20);
    
            ImageView imageView = new ImageView(
                new Image(imageURL)
            );
    
            Button export = new Button("Export");
            export.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent actionEvent) {
                    exportToPDF(layout, PDF_PATH);
                }
            });
    
            layout.setStyle("-fx-font-size: 20px;");
            layout.setAlignment(Pos.CENTER);
            layout.setPadding(new Insets(20));
            layout.getChildren().setAll(
                export,
                imageView
            );
    
            stage.setScene(
                new Scene(
                    new Group(
                        layout
                    )
                )
            );
    
            stage.show();
        }
    
        /**
         * Snapshots the provided node.
         * Encodes the snapshot in a pdf.
         * Saves the pdf to the provided file path.
         * Opens the pdf in the default system web browser.
         *
         * @param node the node for which the snapshot is to be taken.
         * @param filePath the path where the pdf is to be saved.
         */
        private void exportToPDF(Node node, String filePath){
            PDDocument doc = null;
            PDPage page = null;
            PDPageContentStream content = null;
            PDXObjectImage ximage = null;
    
            try {
                // snapshot the node and convert it to an awt buffered image.
                Image fxImage = node.snapshot(null, null);
                BufferedImage bufferedImage = SwingFXUtils.fromFXImage(fxImage, null);
    
                // create a pdf containing the snapshot image.
                doc = new PDDocument();
                page = new PDPage();
                doc.addPage(page);
    
                content = new PDPageContentStream(doc, page);
    
                // alternate path A => try to create a PDJpeg from a jpegInputStream.
                ximage = createPDJpegFromJpegStream(doc, bufferedImage);
    
                // alternate path B => try to create a PDJpeg from directly from a BufferedImage directly.
                // ximage = createPDJpegFromBufferedImage(doc, bufferedImage);
    
                content.drawImage(ximage, 0, 0);
                content.close();
    
                // save the created image to disk.
                doc.save(filePath);
    
                System.out.println("Exported PDF to: " + filePath);
    
                // show the generated pdf in a web browser.
                // (if the browser is pdf enabled, this will display the pdf in the web browser).
                getHostServices().showDocument(filePath);
            } catch(IOException | COSVisitorException ie) {
                ie.printStackTrace();
            } finally {
                try {
                    if (content != null) { content.close(); }
                    if (doc     != null) { doc.close(); }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        // #### THIS METHOD DOES NOT FUNCTION AS EXPECTED
        // alternate path => try to create a PDJpeg from a jpegInputStream.
        // when using a jpeg stream this doesn't work, the created pdf is not well formed and
        // you end up with adobe pdf reader running out of memory trying to read the resultant pdf.
        // Also outputs a weird message that I currently don't understand =>
        //    INFO: About to return NULL from unhandled branch. filter = COSName{DCTDecode}
        private PDXObjectImage createPDJpegFromJpegStream(PDDocument doc, BufferedImage bufferedImage) throws IOException {
            // provide the buffered image data as input to a jpeg input stream.
            ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
            ImageOutputStream jpegImageOutputStream = ImageIO.createImageOutputStream(jpegOutputStream);
            ImageIO.write(bufferedImage, "jpeg", jpegImageOutputStream);
            InputStream jpegInputStream = new ByteArrayInputStream(
                    Arrays.copyOf(jpegOutputStream.toByteArray(), jpegOutputStream.size())
            );
    
            // output created jpg file for debugging purposes
            // => when you view it is pink due to (I believe) an ImageIO bug.
            // you can see how the resultant image is pink by opening the image file named in system.out in any image viewer.
            // this improper encoding of the jpeg data may be why the subsequent use of it to generate a pdf
            // will generate a an invalid pdf.
            File file = new File("output.jpg");
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(Arrays.copyOf(jpegOutputStream.toByteArray(), jpegOutputStream.size()));
            System.out.println(file.getAbsolutePath());
    
            return new PDJpeg(doc, jpegInputStream);
        }
    
        // #### THIS METHOD DOES NOT FUNCTION AS EXPECTED
        // alternate path => try to create a PDJpeg from directly from a BufferedImage directly, get the following exception:
        // Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Raster IntegerInterleavedRaster: width = 552 height = 616 #Bands = 1 xOff = 0 yOff = 0 dataOffset[0] 0 is incompatible with ColorModel ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace@125fe1b6 transparency = 1 has alpha = false isAlphaPre = false
        //    at java.awt.image.BufferedImage.<init>(BufferedImage.java:630)
        // Browsing the awt PDJpeg and awt code it appears that the BufferedImage returned by JavaFX uses a
        // SinglePixelPackedSampleModel, but PDJpeg required the buffered image to use a ComponentColorModel
        // and the two are incompatible.  So the bufferedimage needs to be re-encoded to a compatible
        // raster format that utilizes a SampleModel (i.e. a ComponentColorModel) that is acceptable by PDJpeg.
        //
        private PDXObjectImage createPDJpegFromBufferedImage(PDDocument doc, BufferedImage bufferedImage) throws IOException {
            return new PDJpeg(doc, bufferedImage);
        }
    
        public static void main(String[] args) { launch(args); }
    }
    

答案 1 :(得分:1)

我通过将图像转换为PNG并将其首先写入输出流并将转换后的PNG从输出流读取到BufferedImage,将快照转换为PDF。

最终的BufferedImage在PDJpeg中呈现没有任何问题。

这是我的代码。

public static BufferedImage generate_png_from_container(Node node) {
        SnapshotParameters param = new SnapshotParameters();
        param.setDepthBuffer(true);
        WritableImage snapshot = node.snapshot(param, null);
        BufferedImage tempImg = SwingFXUtils.fromFXImage(snapshot, null);
        BufferedImage img = null;
        byte[] imageInByte;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(tempImg, "png", baos);
            baos.flush();
            imageInByte = baos.toByteArray();
            baos.close();
            InputStream in = new ByteArrayInputStream(imageInByte);
            img = ImageIO.read(in);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       //the final image sent to the PDJpeg
       return img;
}