3

I'm trying to scale my component so it can fit on a single printed page (portrait or landscape)

 gDiagram.getComponent()

is the component (JPanel) I'm trying to print.

Here's what I've got so far based on How can I print a single JPanel's contents?

/**
 * Prints the diagram.
 */
public void printDiagram() {
    PrinterJob pj = PrinterJob.getPrinterJob();
    pj.setJobName(" Print Component ");

    pj.setPrintable(new Printable() {
        @Override
        public int print(Graphics g, PageFormat pf, int pageNumber)
                throws PrinterException {
            // TODO Auto-generated method stub
            if (pageNumber > 0) {
                return Printable.NO_SUCH_PAGE;
            }

            Graphics2D g2 = (Graphics2D) g;
            g2.translate(pf.getImageableX(), pf.getImageableY());

            double sx = pf.getImageableWidth() / gDiagram.getComponent().getWidth();
            double sy = pf.getImageableHeight() / gDiagram.getComponent().getHeight();

            gDiagram.getComponent().paint(g2);
            g2.scale(sx, sy);
            return Printable.PAGE_EXISTS;
        }
    });

    if (!pj.printDialog()) {
        return;
    }
    try {
        pj.print();
    } catch (PrinterException ex) {
        System.out.println(ex);
    }
}

I'm not too familiar with graphics, so any help would be appreciated

Community
  • 1
  • 1
Sam Jarman
  • 7,277
  • 15
  • 55
  • 100

1 Answers1

7

The basic concept is to use an AffineTransformation to provide scaling to the resulting output.

In my tests, I was able to take an image of 7680x4800 and get printed on a page of 595x842 (scaled down by something like 93%)

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class PrintTest {

    public static void main(String[] args) {
        new PrintTest();
    }

    public PrintTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                final TestPane imagePane = new TestPane();
                JButton print = new JButton("Print");
                print.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        printComponent(imagePane);
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(imagePane);
                frame.add(print, BorderLayout.SOUTH);
                frame.setSize(200, 200);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage bg;

        public TestPane() {
            try {
                bg = ImageIO.read(new File("path/to/a/image"));
            } catch (IOException ex) {
                Logger.getLogger(PrintTest.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return bg == null ? new Dimension(200, 200) : new Dimension(bg.getWidth(), bg.getHeight());
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (bg != null) {
                int x = (getWidth() - bg.getWidth()) / 2;
                int y = (getHeight() - bg.getHeight()) / 2;
                g2d.drawImage(bg, x, y, this);
            }
            g2d.dispose();
        }
    }

    public void printComponent(Component comp) {
        PrinterJob pj = PrinterJob.getPrinterJob();
        pj.setJobName(" Print Component ");

        pj.setPrintable(new ComponentPrintable(comp));

        if (!pj.printDialog()) {
            return;
        }
        try {
            pj.print();
        } catch (PrinterException ex) {
            System.out.println(ex);
        }
    }

    public class ComponentPrintable implements Printable {

        private Component comp;

        private ComponentPrintable(Component comp) {
            this.comp = comp;
        }

        @Override
        public int print(Graphics g, PageFormat pf, int pageNumber)
                throws PrinterException {
            // TODO Auto-generated method stub
            if (pageNumber > 0) {
                return Printable.NO_SUCH_PAGE;
            }

            // Get the preferred size ofthe component...
            Dimension compSize = comp.getPreferredSize();
            // Make sure we size to the preferred size
            comp.setSize(compSize);
            // Get the the print size
            Dimension printSize = new Dimension();
            printSize.setSize(pf.getImageableWidth(), pf.getImageableHeight());

            // Calculate the scale factor
            double scaleFactor = getScaleFactorToFit(compSize, printSize);
            // Don't want to scale up, only want to scale down
            if (scaleFactor > 1d) {
                scaleFactor = 1d;
            }

            // Calcaulte the scaled size...
            double scaleWidth = compSize.width * scaleFactor;
            double scaleHeight = compSize.height * scaleFactor;

            // Create a clone of the graphics context.  This allows us to manipulate
            // the graphics context without begin worried about what effects
            // it might have once we're finished
            Graphics2D g2 = (Graphics2D) g.create();
            // Calculate the x/y position of the component, this will center
            // the result on the page if it can
            double x = ((pf.getImageableWidth() - scaleWidth) / 2d) + pf.getImageableX();
            double y = ((pf.getImageableHeight() - scaleHeight) / 2d) + pf.getImageableY();
            // Create a new AffineTransformation
            AffineTransform at = new AffineTransform();
            // Translate the offset to out "center" of page
            at.translate(x, y);
            // Set the scaling
            at.scale(scaleFactor, scaleFactor);
            // Apply the transformation
            g2.transform(at);
            // Print the component
            comp.printAll(g2);
            // Dispose of the graphics context, freeing up memory and discarding
            // our changes
            g2.dispose();

            comp.revalidate();
            return Printable.PAGE_EXISTS;
        }
    }

    public static double getScaleFactorToFit(Dimension original, Dimension toFit) {

        double dScale = 1d;

        if (original != null && toFit != null) {

            double dScaleWidth = getScaleFactor(original.width, toFit.width);
            double dScaleHeight = getScaleFactor(original.height, toFit.height);

            dScale = Math.min(dScaleHeight, dScaleWidth);

        }

        return dScale;

    }

    public static double getScaleFactor(int iMasterSize, int iTargetSize) {

        double dScale = 1;
        if (iMasterSize > iTargetSize) {

            dScale = (double) iTargetSize / (double) iMasterSize;

        } else {

            dScale = (double) iTargetSize / (double) iMasterSize;

        }

        return dScale;

    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • should // Print the component comp.printAll(g2); be comp.paintAll(g2); ? If not, what's the difference? :S – Sam Jarman Aug 02 '13 at 03:42
  • There are a number of differences. To start with Swing components are double buffered, this makes the updates more seamless, you don't need this overhead when printing. Also, you can customise the output for printing as well (which you don't really care). Also, if not connected to a native peer (ie not shown on a screen), I've seen components throw an exception. It's `printAll`, there is not `paintAll` method ;) – MadProgrammer Aug 02 '13 at 03:45
  • Cool. Did you mean to have compSize.width for both? / Calcaulte the scaled size... double scaleWidth = compSize.width * scaleFactor; double scaleHeight = compSize.width * scaleFactor; – Sam Jarman Aug 02 '13 at 03:57
  • Also - do we need to take into account the orientation of the paper? and does this affect the centering you're doing? – Sam Jarman Aug 02 '13 at 03:58
  • I wouldn't think so, in landscape, the width and height are already swapped (from memory) - try it ;) – MadProgrammer Aug 02 '13 at 03:59
  • `compSize.width` is a bug, my bad, I'll fix it – MadProgrammer Aug 02 '13 at 04:00
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/34666/discussion-between-sam-jarman-and-madprogrammer) – Sam Jarman Aug 02 '13 at 04:06