views:

317

answers:

4

I'm trying to render the contents of a the Java/Swing Cobra HTML renderer to an offscreen BufferedImage, for use elsewhere in my app:

 slideViewPanel.setDocument(document, rendererContext);
 BufferedImage test = new BufferedImage(300,300,BufferedImage.TYPE_INT_RGB);
 Graphics g = test.getGraphics();
 slideViewPanel.paint(g);

The resulting image in g shows a partially rendered page -- sometimes the contents of the HTMLFrame before the new document was set; sometimes a half-rendered version of the new document. I gather this is because Cobra's setDocument method just schedules the document for re-rendering, but I'm stepping through in the debugger and I don't see a second thread to do re-rendering. Anyone have any insight into what might be happening here?

A: 

Try calling g.dispose()

Guillaume
g.dispose() doesn't help with the incomplete rendering, but thank you for pointing it out -- the API makes it sound like something I ought to be using with Graphics contexts.Looking at the Cobra docs, it looks like "setDocument" call schedules the page for rendering, rather than rendering it on the spot. There doesn't seem to be a corresponding function/event notifier for "rendering complete," though..
Alterscape
Then I guess you need to get the source code and dig into it...
Guillaume
A: 

You'll have to wait until the component is visible to get the image. Try with that:

   htmlPanel.setDocument(document, rendererContext);
   EventQueue.invokeLater(new Runnable() {
   public void run() {
    if (!captureImage(htmlPanel, "test.jpg")){
     EventQueue.invokeLater(this);
     System.out.println("Encolado");
    }else {
     System.out.println("capturado");
    }
   }
  });

Having captureImage as:

  public static boolean captureImage(Component component, String fileName) {
  boolean captured = false;
  if (component.isVisible()) {
   Dimension size = component.getSize();
   BufferedImage image = new BufferedImage(size.width, size.height,
     BufferedImage.TYPE_INT_RGB);

   component.paint(image.getGraphics());
   captured = true;
   try {
    ImageIO.write(image, "JPEG", new File(fileName));
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
  return captured;
 }
japtesta
+1  A: 

When the page has been parsed, you need to wait until all images are loaded before laying out the component.

Intercept all image loading to make sure all images are completely loaded before laying out the component. I did this by overiding HtmlDocumentImpl.loadImage. (Had to subclass DocumentBuilderImpl and overriding createDocument to make it work.) I used a semaphore to wait until the result of a image.getWItdh is available.

I had to set up a timer around the parsing, as some scripts may loop and never return. I have no idea if there is a better way of solving this.

Regarding the layout, there is a a deal of cargo cult and experimentation that lead to the snipped below , so maybe you can try to remove some things.

            htmlPanel.setVisible(true); 
    htmlPanel.setPreferredWidth(DEFAULT_PAGE_WIDTH);
    logger.info("Calculating preferred size");

    // Get the preferred heigth for the current width.
    Dimension psvz = htmlPanel.getPreferredSize();
    Dimension min = htmlPanel.getMinimumSize();

    logger.info("prf :" + psvz);
    logger.info("min :" + min);

    // Enlarge to the minimum width (with a limit)
    int width = Math.min(MAX_PAGE_WIDTH, Math.max(DEFAULT_PAGE_WIDTH,
            psvz.width));
    int height = psvz.height;

    logger.info("width :" + width);
    logger.info("heigth :" + height);

    htmlPanel.setSize(width, height);

            // actually, htmlPanel is a subclass, and this method exposes validateTree. It may work without it.
    htmlPanel.forceValidateTree(); 

    htmlPanel.doLayout();

    setImageSize(width);
    logger.info("actual size:" + htmlPanel.getSize());

I couldn't figure out what a JFrame does with the HtmlPanel so that it paints its children properly. I had to paint using the children component; Ie either the htmlPanel.getBlockRenderable() or the frameset.

Images are painted asychronous, (and the paints might abort), so before the BufferedImage can be used, all the image paints must be completed.

I used a delegating graphics2d object overriding all the image paint methods to wait until the painting is completed.

After that, i found that the html might benefit from a re-layout as all images sizes are known, so i invalidate the component and do the layout and painting again (or actually, I just call the code twice...)

After that, the BufferedImage can be used. Maybe there is a simpler way.

KarlP
A: 

Actually, surround your 'component.paint(image.getGraphics());' with a 'SwingUtilities.invokeAndWait()' as in:

    try {
    SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
            component.paint(image.getGraphics());
        }
    });
    ImageIO.write(image, "png", file);
} catch (Exception e) {
    } finally {
    }
redBeard