2

So yeah what I am trying here is printing a BufferedImage, all works just fine until you see the outcome. The outcome is to big, the print is to large and doesn't it scales up everything when printing for some reason. I used ((MM * DPI)/25,4) to calculate the correct pixel length according to paper size from Millimeters but when I print it its to big.

This is the code I wrote for it:

package frik.main;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
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 javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;

import java.awt.event.*;

import javax.swing.*;
import frik.data.Config;
import frik.utils.ImgUtil;

public class Previewer implements Config, Printable, ActionListener{

    private JFrame Frame;
    private JPanel ImagePanel;
    private JLabel PicLabel;
    private JButton PrintButton;
    private static BufferedImage before;
    private static boolean Scaled;

    public Previewer(BufferedImage Image, boolean scaled){
        this.before = Image;
        this.Scaled = scaled;
        loop();

    }

    public int print(Graphics g, PageFormat pf, int page) throws PrinterException{
        if (page > 0) { 
            return Printable.NO_SUCH_PAGE;
        }
        Graphics2D g2d = (Graphics2D)g;
        g2d.translate(pf.getImageableX(), pf.getImageableY());

        g.drawImage(0, 0, null);

        return Printable.PAGE_EXISTS;
    }

    public void actionPerformed(ActionEvent e){
        PrinterJob job = PrinterJob.getPrinterJob();
        job.setPrintable(this);
        boolean ok = job.printDialog();
        if (ok) {
            try {
                job.print();
            } catch (PrinterException ex) {
                JOptionPane.showMessageDialog(null, "The Printjob did not successfully complete.", "Print Failure.", JOptionPane.WARNING_MESSAGE);
            }
        }
    }

    public void loop(){
        UIManager.put("swing.boldMetal", Boolean.FALSE);
        Frame = new JFrame("Mold Preview");
        ImagePanel = new JPanel();
        PrintButton = new JButton("Print Mold");

        Frame.addWindowListener(new WindowAdapter() {
               public void windowClosing(WindowEvent e) {System.exit(0);}
            });

        if(Scaled){
            PicLabel = new JLabel(new ImageIcon(ImgUtil.scaleImage(PAPER_WIDTH / 3, PAPER_HEIGHT / 3, before)));
        }else if (!Scaled){
            PicLabel = new JLabel(new ImageIcon(before));
        }

        ImagePanel.setBackground(Color.orange);
        ImagePanel.add(PicLabel);
        Frame.add("Center", ImagePanel);

        PrintButton.addActionListener(this);
        Frame.add("North", PrintButton);

        Frame.pack();
        Frame.setVisible(true);
        Frame.setResizable(false);

    }

    public static void main(String args[]){
        new Previewer(before, Scaled);
        //////////////////////////////
    }
}

So if someone could help me scale the image before printing it according to the printers scale factor, because I am assuming the printers DPI is causing this, so that the image is the exact size in Millimeters when printed exact to the size input in pixels.
That would be great.

Maarten
  • 635
  • 1
  • 9
  • 29
  • I'm not sure that you calculation makes sense `((MM * DPI)/25.4)` DPI should be "Dots Per Inch"... – MadProgrammer Aug 27 '13 at 07:59
  • 1
    Check out [this example](http://stackoverflow.com/questions/13993692/how-to-produce-physical-dimension-of-an-image-drawn-using-java/14000933#14000933) for the calculation of Cm's to pixels, [this example](http://stackoverflow.com/questions/12173515/fit-image-into-the-printing-area/12174371#12174371) for fitting an image into a printable area and [this example](http://stackoverflow.com/questions/17904518/fit-scale-jcomponent-to-page-being-printed/17961911#17961911) for fitting a component into a printable area... – MadProgrammer Aug 27 '13 at 08:04
  • Well for example photoshop uses this to scale your canvas as well. Lets say you want you canvas 11.5 cm you get 325.98px(11.5 * 72(java standard dpi))/2.54 = 325.98 . The millimeters is just for the clients use. But the outcome for photoshop will ofcourse depend on the dpi you will use. – Maarten Aug 27 '13 at 08:07
  • @MadProgrammer that is nice and all, But it still doesn't tell me scale factor of the printer will be. I can calculate it for my own printer by measuring but it will vary from printer to printer so if a clients uses another printer it will clusterf*ck itself. – Maarten Aug 27 '13 at 08:13
  • By default, it's typically, 72dpi, unless you specify a different requirement in the PrinterAttributes. But, most of the examples don't care. They use the actual physical paper size. For your preview, you "could" assume a dpi of 72 or even 96 for the screen – MadProgrammer Aug 27 '13 at 09:11
  • @MadProgrammer I tested if my calculations are correct, lets say I ImageIO the BufferedImage instead of Print. So I then have a png creating using 72 dpi width `(124 * 72)/25.4` and `(265 * 72)/25.4` then printing it out using paint or what ever to A4 paper. And then measure it with a rules it is exactly correct. But when I use the print api it suddenly = incorrect. – Maarten Aug 27 '13 at 09:41
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36324/discussion-between-rose-blax-and-madprogrammer) – Maarten Aug 27 '13 at 11:33

1 Answers1

5

I'm not sure if this is an answer per se, but it solves one of the "niggly" issues I'm having.

I think the problem I have is you don't have a source DPI, so it's not possible to convert from one context to another. Let's say you have a image of 200x200, what does that actually mean?

Without the DPI it's meaningless. If the image is 300dpi, then we could use pixels / dpi = inches = 200 / 72 = 0.667 inches. Then we can convert that to pixels @ 72dpi using inches * dpi = 0.667 * 72 = 48

Now, the question the becomes, how do I get the DPI of an image. That's not nearly as easy as it sounds...

import core.ui.UIUtilities;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class TestDPI {

    public static final float INCH_PER_MM = 25.4f;

    public static void main(String[] args) {
        File imageFile = new File("/path/to/your/image");
        ImageInputStream iis = null;
        try {
            iis = ImageIO.createImageInputStream(imageFile);
            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            if (!readers.hasNext()) {
                throw new IOException("Bad format, no readers");
            }
            ImageReader reader = readers.next();
            reader.setInput(iis);
            IIOMetadata meta = reader.getImageMetadata(0);

            Node root = meta.getAsTree("javax_imageio_1.0");
            NodeList nl = root.getChildNodes();
            float horizontalPixelSize = 0;
            float verticalPixelSize = 0;
            for (int index = 0; index < nl.getLength(); index++) {
                Node child = nl.item(index);
                if ("Dimension".equals(child.getNodeName())) {
                    NodeList dnl = child.getChildNodes();
                    for (int inner = 0; inner < dnl.getLength(); inner++) {
                        child = dnl.item(inner);
                        if ("HorizontalPixelSize".equals(child.getNodeName())) {
                            horizontalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
                        } else if ("VerticalPixelSize".equals(child.getNodeName())) {
                            verticalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
                        }
                    }
                }
            }
            // As "I" understand it.  The horizontalPixelSize and verticalPixelSize
            // are the number of millimeters per pixel that should be occupied...
            System.out.println((INCH_PER_MM / horizontalPixelSize) + "x" + (INCH_PER_MM / verticalPixelSize));

        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                iis.close();
            } catch (Exception e) {
            }
        }
    }
}

Update with preview example

This example basically uses the images own DPI and a target DPI to produce a "print preview"

My test image is 1667x1609 @ 300dpi

300dpi test...

  • Original size = 1667x1609
  • cmSize = 14.11396110802889x13.622893474996092
  • Target (pixel) size = 1667x1609

enter image description here

72dpi test...

  • Original size = 1667x1609
  • cmSize = 14.11396110802889x13.622893474996092
  • Target (pixel) size = 400x386

enter image description here

import static core.ui.ImageUtilities.getScaleFactor;
import static core.ui.ImageUtilities.getScaleFactorToFit;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class TestPrintPreview {

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

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

                File imageFile = new File("C:\\hold\\thumbnails\\RentAZilla-300dpi.png");

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new PreviewPane(imageFile, 300)));
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    // The size of an A4 sheet in CMs
    public static final double[] A4_PAPER_SIZE = new double[]{21.0, 29.7};
    // The number of CMs per Inch
    public static final double CM_PER_INCH = 0.393700787d;
    // The number of Inches per CMs
    public static final double INCH_PER_CM = 2.545d;
    // The numer of Inches per mm's
    public static final double INCH_PER_MM = 25.45d;

    public class PreviewPane extends JPanel {

        private BufferedImage img;
        private float targetDPI;

        private BufferedImage gridBackground;

        public PreviewPane(File imageFile, float outputDPI) {
            // This determines the output DPI we want...
            targetDPI = outputDPI;
            try {
                // Get the DPI from the image...
                double[] imgDPI = getDPI(imageFile);
                // Read the image
                img = ImageIO.read(imageFile);

                // Output the original size...
                System.out.println("Original size = " + img.getWidth() + "x" + img.getHeight());

                // Calculate the size of the image in cm's
                double cmWidth = pixelsToCms(img.getWidth(), imgDPI[0]);
                double cmHeight = pixelsToCms(img.getHeight(), imgDPI[1]);

                System.out.println("cmSize = " + cmWidth + "x" + cmHeight);

                // Calculate the new image size based on the target DPI and
                // the cm size of the image...
                int imgWidth = (int) Math.round(cmsToPixel(cmWidth, targetDPI));
                int imgHeight = (int) Math.round(cmsToPixel(cmHeight, targetDPI));
                System.out.println("Target size = " + imgWidth + "x" + imgHeight);

                // Create a scaled instance of the image to fit within the 
                // target boundries
                img = getScaledInstanceToFit(img, new Dimension(imgWidth, imgHeight));

            } catch (IOException ex) {
                Logger.getLogger(TestPrintPreview.class.getName()).log(Level.SEVERE, null, ex);
            }
            setBackground(Color.WHITE);
        }

        @Override
        public Dimension getPreferredSize() {
            // Return the size of the component based on the size of 
            // an A4 sheet of paper and the target DPI
            return new Dimension(
                    (int) Math.round(cmsToPixel(A4_PAPER_SIZE[0], targetDPI)),
                    (int) Math.round(cmsToPixel(A4_PAPER_SIZE[1], targetDPI)));
        }

        /**
         * Generates a grid of 1x1 cm cells.  This is used to allow you
         * to compare the differences of different DPI and ensure that the
         * output is what you are expecting...
         * @return 
         */
        protected BufferedImage getGridBackground() {
            if (gridBackground == null) {
                // Calculate the width and height we need...
                int width = (int) Math.round(cmsToPixel(A4_PAPER_SIZE[0], targetDPI));
                int height = (int) Math.round(cmsToPixel(A4_PAPER_SIZE[1], targetDPI));

                // Create the grid...
                gridBackground = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = gridBackground.createGraphics();
                // Calculate the size of each cell (1cm square)
                double cmAsPixel = cmsToPixel(1, targetDPI);
                float xPos = 0;
                float yPos = 0;
                g2d.setColor(new Color(225, 0, 0, 128));
                int count = 0;
                Font font = g2d.getFont();
                g2d.setFont(font.deriveFont(8f));
                FontMetrics fm = g2d.getFontMetrics();
                // Draw the horizontal lines
                while (xPos < gridBackground.getWidth()) {
                    g2d.draw(new Line2D.Float(xPos, 0, xPos, gridBackground.getHeight()));
                    // Add the text markers...
                    String text = (count++) + "cm";
                    float x = xPos - fm.stringWidth(text);
                    g2d.drawString(text, x, fm.getAscent());
                    xPos += cmAsPixel;
                }
                // Draw the vertical lines
                count = 0;
                while (yPos < gridBackground.getHeight()) {
                    g2d.draw(new Line2D.Float(0, yPos, gridBackground.getWidth(), yPos));
                    // Add the text markers
                    String text = (count++) + "cm";
                    float y = (yPos - fm.getHeight()) + fm.getAscent();
                    g2d.drawString(text, 0, y);
                    yPos += cmAsPixel;
                }
                g2d.dispose();
            }
            return gridBackground;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            // Paint the image...
            g2d.drawImage(img, 0, 0, this);
            // Paint the grid...
            g2d.drawImage(getGridBackground(), 0, 0, this);
            g2d.dispose();
        }
    }

    /**
     * Converts the given pixels to cm's based on the supplied DPI
     * @param pixels
     * @param dpi
     * @return 
     */
    public static double pixelsToCms(double pixels, double dpi) {
        return inchesToCms(pixels / dpi);
    }

    /**
     * Converts the given cm's to pixels based on the supplied DPI
     * @param cms
     * @param dpi
     * @return 
     */
    public static double cmsToPixel(double cms, double dpi) {
        return cmToInches(cms) * dpi;
    }

    /**
     * Converts the given cm's to inches
     * @param cms
     * @return 
     */
    public static double cmToInches(double cms) {
        return cms * CM_PER_INCH;
    }

    /**
     * Converts the given inches to cm's 
     * @param inch
     * @return 
     */
    public static double inchesToCms(double inch) {
        return inch * INCH_PER_CM;
    }

    /**
     * Gets the DPI for the specified image.  This does return the horizontal
     * and vertical DPI, but you could conceivably use just use one of the values
     * @param imageFile
     * @return
     * @throws IOException 
     */
    public double[] getDPI(File imageFile) throws IOException {

        double[] dpi = new double[]{72, 72};

        ImageInputStream iis = null;
        try {
            iis = ImageIO.createImageInputStream(imageFile);
            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            if (!readers.hasNext()) {
                throw new IOException("Bad format, no readers");
            }
            ImageReader reader = readers.next();
            reader.setInput(iis);
            IIOMetadata meta = reader.getImageMetadata(0);

            Node root = meta.getAsTree("javax_imageio_1.0");
            NodeList nl = root.getChildNodes();
            float horizontalPixelSize = 0;
            float verticalPixelSize = 0;
            for (int index = 0; index < nl.getLength(); index++) {
                Node child = nl.item(index);
                if ("Dimension".equals(child.getNodeName())) {
                    NodeList dnl = child.getChildNodes();
                    for (int inner = 0; inner < dnl.getLength(); inner++) {
                        child = dnl.item(inner);
                        if ("HorizontalPixelSize".equals(child.getNodeName())) {
                            horizontalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
                        } else if ("VerticalPixelSize".equals(child.getNodeName())) {
                            verticalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
                        }
                    }
                }
            }

            dpi = new double[]{(INCH_PER_MM / horizontalPixelSize), (INCH_PER_MM / verticalPixelSize)};
        } finally {
            try {
                iis.close();
            } catch (Exception e) {
            }
        }

        return dpi;
    }

    /**
     * Returns a scaled instance of the image to fit within the specified 
     * area.  This means that the image is guaranteed to be <= size.width and
     * <= size.height
     * @param img
     * @param size
     * @return 
     */
    public static BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
        double scaleFactor = getScaleFactorToFit(img, size);
        return getScaledInstance(img, scaleFactor);
    }

    public static double getScaleFactorToFit(BufferedImage img, Dimension size) {

        double dScale = 1;
        if (img != null) {
            int imageWidth = img.getWidth();
            int imageHeight = img.getHeight();

            dScale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
        }

        return dScale;

    }

    /**
     * Returns the required scale factor to fit the original size into the toFit
     * size.
     * @param original
     * @param toFit
     * @return 
     */
    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;

    }

    /**
     * Returns the scale factor required to go from the master size to the 
     * target size
     * @param iMasterSize
     * @param iTargetSize
     * @return 
     */
    public static double getScaleFactor(int iMasterSize, int iTargetSize) {
        return (double) iTargetSize / (double) iMasterSize;
    }

    /**
     * Returns a scaled instance of the image based on the supplied scale factor.
     * 
     * The images width and height are multiplied by the supplied scale factor
     * @param img
     * @param dScaleFactor
     * @return 
     */
    protected static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
        BufferedImage imgScale = img;

        int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
        int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);

        if (dScaleFactor <= 1.0d) {
            imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight);
        } else {
            imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight);
        }
        return imgScale;
    }

    /**
     * Scales the specified image down to be less then equal to the target width
     * and height.
     * 
     * The image is scaled using a divide an conquer approach to provide
     * the best scaling possible
     * @param img
     * @param targetWidth
     * @param targetHeight
     * @return 
     */
    protected static BufferedImage getScaledDownInstance(BufferedImage img,
            int targetWidth,
            int targetHeight) {

        int type = (img.getTransparency() == Transparency.OPAQUE)
                ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

        BufferedImage ret = (BufferedImage) img;

        if (targetHeight > 0 || targetWidth > 0) {
            int w, h;
            w = img.getWidth();
            h = img.getHeight();

            do {
                if (w > targetWidth) {
                    w /= 2;
                    if (w < targetWidth) {
                        w = targetWidth;
                    }
                }

                if (h > targetHeight) {
                    h /= 2;
                    if (h < targetHeight) {
                        h = targetHeight;
                    }
                }

                BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
                Graphics2D g2 = tmp.createGraphics();
                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2.drawImage(ret, 0, 0, w, h, null);
                g2.dispose();

                ret = tmp;
            } while (w != targetWidth || h != targetHeight);
        } else {
            ret = new BufferedImage(1, 1, type);
        }

        return ret;

    }

    /**
    /**
     * Scales the specified image up
     * 
     * The image is scaled using a divide an conquer approach to provide
     * the best scaling possible
     * @param img
     * @param targetWidth
     * @param targetHeight
     * @return 
     */
    protected static BufferedImage getScaledUpInstance(BufferedImage img,
            int targetWidth,
            int targetHeight) {

        int type = BufferedImage.TYPE_INT_ARGB;

        BufferedImage ret = (BufferedImage) img;
        int w, h;
        w = img.getWidth();
        h = img.getHeight();

        do {
            if (w < targetWidth) {
                w *= 2;
                if (w > targetWidth) {
                    w = targetWidth;
                }
            }

            if (h < targetHeight) {
                h *= 2;
                if (h > targetHeight) {
                    h = targetHeight;
                }
            }

            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
            tmp = null;
        } while (w != targetWidth || h != targetHeight);
        return ret;
    }
}
halfer
  • 19,824
  • 17
  • 99
  • 186
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366