10
votes

I want to make an image in Java and print it on a 300dpi label printer on a label, sized 150 x 100 mm. How can I make the image so that a line (or whatever kind of element) is printed exactly at position (10,10) (in millimeters), and that line is ended at position (10,50)?

In other words: My challenge is not how to make a line (I'm using Graphics2D, bufferedImage), but it is how to be able to tell exactly where this line has to be (in millimeters) on the label.

Any Ideas?

3
Generally, printing is done at 72 dpi (okay, it's not, but the API thinks it is). So start by trying not to worry about the 300dpi and focus on converting from 72 dpi to what ever you needMadProgrammer

3 Answers

7
votes

Java's print API basically works on the assumption that everything is done at 72 dpi. This means that you can use this as bases for converting to/from different measurements...

This just means you need and start value and target measurement...

// 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 number of Inches per mm's
public static final double INCH_PER_MM = 25.45d;

/**
 * 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;
}

So, in order to print the image at 10mmx10mm, you would need to convert this to pixel's per inch

double point = cmsToPixel(1, 72);

You're probably also going to need to think about maybe downsizing the image to fit into the printable area.

For some examples...

Update with test results

So I did some tests (code to follow)...

First, I set up a PrintRequestAttributeSet to request only print services capable of supporting 300x300 dpi...

PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
aset.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
aset.add(new MediaPrintableArea(0, 0, 150, 100, MediaPrintableArea.MM));

When printed, my Printable was passed an imageable area of 425.20 x 283.46 pixels, which equates to 15.03 x 10.02 cm @ 72dpi (roughly). This is how Java works, it's basic print API has always worked on the assumption of 72dpi.

So. If I prepare an image of 10 x 50 mm @ 72 DPI, I get an image size of 28.346 x 141.732 pixels, which will easily fit within the imageable area (of 425.20 x 283.46).

However, if I use 300 dpi, I get a image size of 118.11 x 590.551 pixels, which is going to run us into trouble, requiring us to downscale the image...

This, actually, may be desirable, you will have to perform some testing to find out.

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
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.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.PrinterResolution;

public class TestHiResPrinting {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
                aset.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
                aset.add(new MediaPrintableArea(0, 0, 150, 100, MediaPrintableArea.MM));

                PrinterJob pj = PrinterJob.getPrinterJob();
                pj.setPrintable(new PrintTask());

                if (pj.printDialog(aset)) {
                    try {
                        pj.print(aset);
                    } catch (PrinterException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });
    }

    // 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 number of Inches per mm's
    public static final double INCH_PER_MM = 25.45d;

    /**
     * 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;
    }

    public static class PrintTask implements Printable {

        private BufferedImage img;

        public PrintTask() {
            double width = cmsToPixel(1, 72);
            double height = cmsToPixel(5, 72);

            img = new BufferedImage((int) Math.round(width), (int) Math.round(height), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = img.createGraphics();
            g2d.setColor(Color.RED);
            g2d.draw(new Rectangle2D.Double(0, 0, width - 1, height - 1));
            g2d.draw(new Line2D.Double(0, 0, width, height));
            g2d.draw(new Line2D.Double(0, height, width, 0));
            g2d.dispose();
        }

        @Override
        public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
            int result = NO_SUCH_PAGE;
            if (pageIndex < 2) {
                Graphics2D g2d = (Graphics2D) graphics;
                double width = pageFormat.getImageableWidth();
                double height = pageFormat.getImageableHeight();

                System.out.println("Page width = " + width + " = " + pixelsToCms(width, 72));
                System.out.println("Page height = " + height + " = " + pixelsToCms(height, 72));

                g2d.translate((int) pageFormat.getImageableX(),
                                (int) pageFormat.getImageableY());
                double x = cmsToPixel(1, 72);
                double y = cmsToPixel(1, 72);
                System.out.println("Draw At " + x + "x" + y);
                g2d.drawRect(0, 0, (int)width - 1, (int)height - 1);
                g2d.drawImage(img, (int)x, (int)y, null);
                result = PAGE_EXISTS;
            }
            return result;
        }

    }
}
-1
votes

Well there are lots of things to consider, most of which is basic math. I'm not particular familiar with Java2D, so I can't tell you if there are any helper functions, but here is the math:

150 x 100 millimeters is roughly 6x4 inches. At 300 DPI, you need a pixel resolution of 1800x1200.

1" is equal to 300 Pixels, and equal to 25.4 mm this mean s that 1 mm is equal to about 12 pixels (11.8).

So if you want to make a line starting at 10x10mm, you need to multiply that with the amount of pixels in a mm, in this case 12. So start drawing your line at 120x120 pixels.

Likewise if you need to end the line at 10x50mm, you need to end your line drawing at 120x600.

The math is different in each case, depending on what resolution the printer prints in, but for the question asked, these numbers should suffice.

Hope it helped.

-1
votes

In my tests using a Brother DPC-135C printer by running an edited program by MadProgrammer above by commenting the following lines:

//   aset.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
//   aset.add(new MediaPrintableArea(0, 0, 150, 100, MediaPrintableArea.MM));

...
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
   g2d.drawRect((int) pageFormat.getImageableX(), (int) pageFormat.getImageableY(), (int) pageFormat.getImageableWidth(), (int) pageFormat.getImageableHeight());
...

I think the g2d.drawRect() above correctly covers the entire mediaprintablearea as defined in javadoc (PageFormat) pageFormat.getImageableWidth() and pageFormat.getImageableHeight() returning a value in 1/72 of an inch. For example pageFormat.getImageableWidth() returning 451.2755 means 451.2755 / 72 = ~6.26 inches width of printable area. So starting from pageFormat.getImageableX(), (int) pageFormat.getImageableY() every millimeter is about 2.8346 units (72 units is 1 inch, 1 inch = 25.4 mm). So to draw the image (The pixels of the image are scaled to fit to the specified width and height):

 g2d.drawImage(img, (int) (pageFormat.getImageableX() + 2.8346 * 10),   // start printing at 10 mm from left of printable area
                    (int) (pageFormat.getImageableY() + 2.8346 * 10),   // start printing at 10 mm from top of printable area
                    (int) 2.8346 * 100,   // width of 100 mm
                    (int) 2.8346 * 150,   // height of 150 mm
                    this);

In the printing dialog, you will need to remove the margins. Setting the dot per inch like the code above by MadProgrammer I think messes the measurement of what exactly is an inch in the printout (In relation of what is stated in the SDK that pageFormat.getImageableWidth() and pageFormat.getImageableHeight() returns in 1/72 of an inch). In my tests, I measured the printed image using a ruler and it's pretty accurate.