Questions about PBuffers

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Questions about PBuffers

Martin Hegedus
My goal is to create a png image for printing.  So, I've created an offscreen drawable, drawn to it, and then extracted a buffered image using Screenshot.  I'm running under Linux.  Haven't tried windows yet.

I've got two questions.

First, does the memory required for pbuffer come from the video card or mother board?  If it is from the video card I'm concerned that some may not have enough memory especially when driving several monitors from a laptop.  From the internet I gather it is video card memory.  Therefore, if allocating a pbuffer fails I would like to use a regular pixel map.

Second, when using a pbuffer my code works, but when using a pixel map, I just get the black background (even though I've set the clear color to white.)  So I gather no drawing, or clearing, is being done.  Is setting the pbuffer to 'false' a valid option?  Or is this possibly a bug?

Thanks.

Here is a snip-it of my code so one can get an idea of what I'm doing.  I can provide more if required.

GLCapabilities glCaps = new GLCapabilities(null);
glCaps.setRedBits(8);
glCaps.setBlueBits(8);
glCaps.setGreenBits(8);
glCaps.setAlphaBits(8);
glCaps.setDoubleBuffered(false);
glCaps.setOnscreen(false);
glCaps.setPBuffer(true);  // this code fails to draw if this is set to false.
glCaps.setHardwareAccelerated(false);

GLDrawableFactory fac = GLDrawableFactory.getDesktopFactory();
GLDrawable buf        = fac.createOffscreenDrawable(null, glCaps, null, width, height);

GLContext context =  buf.createContext(null);
context.makeCurrent();
GL2 gl = context.getGL().getGL2();
gl.glClearColor(1.0f,1.0f,1.0f,1.0f);  // set clear color to white
gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT );
Draw(gl);
BufferedImage img = Screenshot.readToBufferedImage(width,height);
context.release();
Reply | Threaded
Open this post in threaded view
|

Re: Questions about PBuffers

Gene
Of course manufacturers are free to put PBuffers in whatever memory they choose. Worse, they don't have to implement PBuffers at all.

Most will put them on the card, but some "steal" motherboard memory even for the display surface. So you just don't know.

A full page image at 1200 dpi and 4 bytes per pixel is about 1/2 gigabyte. I've never tried allocating anything bigger than 4096x4096, which is 8 times smaller, and older cards do not handle that well.

IMO you will have to probe the card to see how big a buffer you can allocate then render in chunks, tiling these into the full image.  This will be especially true if the printer is very hi res or large format.

An additional note: It sounds like you intend to create a PNG file and then print the file.  An alternative is to tile into a Printable GC in order to print directly from Java. It's not hard at all. If you need code, I can dig some out.

Or if you can accept less than full printer resolution, you can scale whatever the PBuffer size is into the Printable with 2D smoothing.
Reply | Threaded
Open this post in threaded view
|

Re: Questions about PBuffers

Martin Hegedus
Hi Gene,

Thanks.  First, I'll admit I don't have in depth knowledge of creating a printing interface for a Java OpenGL application.  It seems straight forward to create a Printable interface for a regular Canvas and then do 2D drawing.  But fusing a Printable interface with a JOGL GLCanvas is not clear to me.  And tiling it makes it even murkier.

So, yes, please pass along some code.  It seems like what you described is what I would like to do.

My overall goal is to print an OpenGL graphic to a variety of printers.  My immediate goal is to print, i.e. stream, a PNG file to a home office HP LaserJet multifunction printer from Linux, Windows, and eventually Mac OS.  I'm assuming (hoping) once that is working I'll also be able to print to ink jet printers.  I have implemented spooling postscript to a file by means of the feedback buffer.  It's not perfect in regards to hidden surfaces, but it will have to do for the time being.  In OpenGL terms my drawing needs are simple.  I'm drawing 3D surface meshes and contour plots.

Thanks.
Reply | Threaded
Open this post in threaded view
|

Re: Questions about PBuffers

Gene
It's not so complicated.  I've appended code to print a BufferedImage to one full page.  The printer's Graphics2D works almost exactly like any Component's.  However, you'll notice that the smaller you make the buffered image in my code, the fuzzier the printout will be as the Graphic2D smoothing algorithm spreads one image pixel over more printer pixels.  So the point of the earlier post is that if you choose to render at a very high resolution to make the printed image high quality, you may not be able to get a buffer big enough to hold the whole image.  Producing tiles is just a question of setting OpenGL's viewport to the correct rectangle and re-rendering.

The tiling logic would work something like this:

0.  Assume the image is Mi rows and Ni columns and the printer's writable area is Mp by Np.
1.  Determine the biggest PBuffer you can get.  Say this is Mb by Nb (normally these are equal).
2.  Let nRows = ceiling(Mi / Mb), the number of rows of tiles. Similarly, nCols = ceiling(Ni / Nb)
3.  Let float Hp = Mp / nRows and Wp = Np / nCols be the height and width of a tile on the printer.  Floats limit roundoff error accumulation.
4.  Draw as follows:
 
for (iTile = 0;  iTile < nRows; iTile++)
  for (jTile = 0; jTile < nCols; jTile++) {
    Render into the PBuffer with viewport (jTile * Nb, iTile * Mb, Mb, Nb);
    Capture the Pbuffer in a BufferedImage.
    DrawImage the BufferedImage to the printer in the rectangle
      [jTile * Wp, iTile * Hp, Wp, Hp]
    using the example of the included code.

Note that if the PBuffer is big enough to hold the whole image, the loops each execute just once.

Note also that it's possible the Printable will call your print() routine multiple times, even to render one page.  It will be doing it's own tiling in this case!

import java.awt.*;
import java.awt.image.*;
import java.awt.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.JobName;
import javax.swing.*;

public class Test2 {

    public class BufferedImagePrintable implements Printable {

        final private BufferedImage image;

        public BufferedImagePrintable(BufferedImage image) {
            this.image = image;
        }

        public int print(Graphics graphics, PageFormat pf, int pageIndex) throws PrinterException {
            if (pageIndex != 0) {
                return NO_SUCH_PAGE;
            }
            final int x = (int)pf.getImageableX();
            final int y = (int)pf.getImageableY();
            final int w = (int)pf.getImageableWidth();
            final int h = (int)pf.getImageableHeight();
            final int s = Math.min(w, h); // maintain aspect
            // The image icon constructor is there just to block until scaling is complete.
            // There might be a way to do this without allocating the intermediate
            // scaled instance, which could be large.  Check drawImage(BufferedImage, ... ).  
            // But then you need a media tracker to block until completion. Printable is not
            // a Component and doesn't implement ImageObserver, a disconnect in the API.
            new ImageIcon(image.getScaledInstance(s, s, BufferedImage.SCALE_SMOOTH))
                    .paintIcon(null, graphics, x, y);
            return PAGE_EXISTS;
        }
    }

    public void run() {
        // Make an image.
        final BufferedImage bufferedImage = new BufferedImage(4096, 4096, BufferedImage.TYPE_3BYTE_BGR);

        // Draw something.  In place of this, you can grab the OpenGL surface.
        Graphics2D g = (Graphics2D)bufferedImage.getGraphics();
        int x = 0;
        int y = 0;
        int w = bufferedImage.getWidth();
        int h = bufferedImage.getHeight();
        g.setColor(Color.white);
        g.fillRect(x, y, w, h);
        x += 100; y += 100;
        w -= 2 * x; h -= 2 * y;
        g.setStroke(new BasicStroke(8f));
        g.setColor(Color.red);
        g.drawRect(x, y, w, h);
        g.drawOval(x, y, w, h);
        g.setColor(Color.black);
        g.drawLine(x, y, x+w, y+h);
        g.drawLine(x+h, y, x, y+h);
        g.dispose();

        PrinterJob job = PrinterJob.getPrinterJob();
        job.setPrintable(new BufferedImagePrintable(bufferedImage));
        HashPrintRequestAttributeSet attr = new HashPrintRequestAttributeSet();
        attr.add(new JobName("BufferedImage", null));
        // You can add attributes here to alter the printer dialog.
        boolean printAccepted = job.printDialog(attr);
        if (printAccepted) {
            try {
                job.print(attr);
            } catch (PrinterException ex) {
                JOptionPane.showMessageDialog(null,
                        "Printing has been canceled!", "Printing halted",
                    JOptionPane.INFORMATION_MESSAGE);
            }
        }
    }

    public static void main (String [] args) {
        new Test2().run();
    }
}
Reply | Threaded
Open this post in threaded view
|

Re: Questions about PBuffers

Martin Hegedus
Thanks Gene.

OK, then the objective is to put the tiling methodology inside the print() method.

Is there a way to query how much memory is available for the pbuffer with jogl?  Or should one try to allocate what is required and if that fails, quarter the requirement, and if that fails, keep quartering and checking.

From what you said in your first post, it sounds like there is a possibility that a pbuffer of any size can not be allocated.  In which case it would be nice to allocate an off screen buffer as a simple pixmap/bitmap surface.  However, my drawing fails when I set pBuffers to false, i.e. glCaps.setPBuffer(false).  The createOffscreenDrawable does return a non null GLDrawable but drawing to it seems to fail, i.e. the image is black.  This is under Linux and I have not tried windows yet.  Unfortunately I'm not sure if I am doing something wrong or a bug exists in jogl.  Does anyone have any thoughts on this?

Thanks.  And I'll be out of town till the end of this month, and probably unable to reply till then.  Happy Holidays!