DIN A4 ausdrucken

Hallo,

ich möchte ein DIN A4 Bild ausdrucken. Der Ausdruck aus Java heraus ist etwas unscharf, speziell bei Schriften. Anders als ein Ausdruck auf Betriebssystem Ebene (Ubuntu).

Kann mir jemand sagen woran das liegt?

Hier der Code

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class PrintA4Test {

	public static double imageableX;
	public static double imageableY;
	public static double imageableWidth;
	public static double imageableHeight;
	
    public static void main(String[] args) throws IOException{

     	BufferedImage image = ImageIO.read(new File("/home/$user/test.png"));
    	
        PrinterJob pj = PrinterJob.getPrinterJob();
        if (pj.printDialog()){
        	
            PageFormat pf = pj.defaultPage();
            Paper paper = pf.getPaper();
         
            imageableX = 0;
            imageableY = 0;
            imageableWidth = pf.getWidth() - (2 * imageableX);
            imageableHeight = pf.getHeight() - (2 * imageableY);
            paper.setImageableArea(imageableX, imageableY, imageableWidth, imageableHeight);
            
            pf.setOrientation(PageFormat.LANDSCAPE);
            pf.setPaper(paper);
            PageFormat validatePage = pj.validatePage(pf);
            pj.setPrintable(new TestPrintable(image), validatePage);
            
            try{
            	
                pj.print();
                
                
            }catch (PrinterException ex){
            	
                ex.printStackTrace();
                
            }
        }
    }

    public static class TestPrintable implements Printable {

        private BufferedImage image;

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

        @Override
        public int print(Graphics g, PageFormat pf, int page) throws PrinterException{
            
        	Graphics2D g2d = (Graphics2D) g.create();
            
        	if (page != 0){
            	
                return NO_SUCH_PAGE;
                
            }
            
            g2d.drawImage(image.getScaledInstance((int)imageableHeight, (int)imageableWidth, BufferedImage.SCALE_SMOOTH), (int)imageableX, (int)imageableY, null);
            
            g2d.dispose();

            return PAGE_EXISTS;
            
        }
    }
}


ich habs leider nie gemacht, aber für mich schreit das danach, dass du das Bild anders skalierst als das System.

Propiere doch mal mit den anderen Scale Properties rum wie das aussieht.

1 Like

Ich habe mehrere Scale Properties ausprobiert. SCALE-SMOOTH liefert noch das beste Ergebnis.

Ich habe noch in paar Infos zum verwendeten Bild: das Bild hat eine Auslösung von 300 dpi. Und eine Größe von 3508x2480 Pixel.

hast du mal nachgesehen wie groß deine Seite die du druckst im Code ist?
Vielleicht machst du es zu klein/groß und dann muss das System noch wild rumskalieren

Mit:

imageableWidth und imageableHeight

hole ich mir, wenn ich das richtig verstanden habe, den maximalen Druckbereich des Druckers.

Diesen Druckbereich verwende ich dann in

g2d.drawImage(image.getScaledInstance((int)imageableHeight, (int)imageableWidth, BufferedImage.SCALE_SMOOTH), (int)imageableX, (int)imageableY, null);

um das Bild auf die maximal mögliche Größe zu skalieren und zu drucken.

aber was kommt bei pf.getWidth() & co ?

getWidth: 595.2755813598633 getHeight: 841.8897399902344

hmmm das ist komisch, das gibt die korrekten Papiergrößen zurück und das Bild ist ja auch groß genug
da bin ich jetzt überfragt

Ok, Danke.

Hier steht, was zu tun ist Using Print Setup Dialogs (The Java™ Tutorials > 2D Graphics > Printing) Öffne einen Print-Dialog, anstatt es selber einzustellen

Disclaimer vorneweg: Ich hab’ mit Drucken aus Java speziell noch nicht viel gemacht. IIRC hab’ ich es mal verwendet, um in eine PDF zu drucken, aber das ist ewig her und war nur ein Experiment.

Erstmal bin ich etwas verwundert, dass du von unscharfen Schriften redest, wenn das, was da gedruckt wird, eine PNG-Datei ist. Ich nehme an, die enthält Text/Schrift? (Der Punkt ist: Es ist nochmal was anderes, wenn man irgendwas drucken will, was direkt in ein Graphics2D/drawString reingepinselt wird).

Asonsten… ist Image.getScaledInstance praktisch nie sinnvoll. Je nachdem, was genau man machen will, gibt es meistens bessere Optionen. Das bezieht sich einmal auf die Kontrollmöglichkeiten (hinsichtlich der Qualität), aber insbesondere auch darauf, das getScaledInstance furchtbar, furchtbar langsam ist.

Eine „Alternative“ zu image.getScaledInstance ist die hier:

package bytewelt;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;

public class NoGetScaledInstance
{
    public static BufferedImage getScaledInstance(
        BufferedImage image, int w, int h)
    {
        BufferedImage i = image;
        if (image.getType() != BufferedImage.TYPE_INT_ARGB)
        {
            i = convert(image, BufferedImage.TYPE_INT_ARGB);
        }
        RenderingHints renderingHints = new RenderingHints(null);
        renderingHints.put(
            RenderingHints.KEY_INTERPOLATION, 
            RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        return scaleWithAffineTransformOp(i, w, h, null);
    }

    private static BufferedImage convert(BufferedImage image, int type)
    {
        BufferedImage newImage =
            new BufferedImage(image.getWidth(), image.getHeight(), type);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }

    private static BufferedImage scaleWithAffineTransformOp(BufferedImage image,
        int w, int h, RenderingHints renderingHints)
    {
        BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
        double scaleX = (double) w / image.getWidth();
        double scaleY = (double) h / image.getHeight();
        AffineTransform affineTransform =
            AffineTransform.getScaleInstance(scaleX, scaleY);
        AffineTransformOp affineTransformOp =
            new AffineTransformOp(affineTransform, renderingHints);
        return affineTransformOp.filter(image, scaledImage);
    }
    

}

Das ist erstmal auch nur eine (fest verdrahtete) Art, Bilder zu skalieren, aber … vielleicht magst du mal ausprobieren, ob es damit schon besser ist. (Wenn nicht, kann man vielleicht irgendwas tweaken. Könnte das Eingabe-Bild hier gepostet werden, damit man einen passenden Fall testen kann?)

1 Like

Ach so, zusätzlich wird vorher noch skaliert, das hatte ich nicht gelesen… das was Marco sagt.

Mein Drucker kann mit 600 oder 1200 dpi drucken, vielleicht hat es auch damit zu tun.

Nach den DPI hatte ich tatsächlich auch geschaut. Zumindest erinnere ich mich, dass ich mal irgendwann ein ~600x600-pixel-Bild „so groß, wie es halt ist“ ausgedruckt habe, und das bei einem 600dpi-Drucker dann halt „1 inch“ war :zany_face:

Aber anscheinend sind die DPI dort schon wegabstrahiert. Zumindest habe ich auf die schnelle nichts explizit DPI-bezogenes in der API (Page etc) gesehen. (Notfalls nochmal schauen, falls die andere Skaliermethode nichts bringt…)

1 Like

Nein, ich kann das Bild nicht posten. Trotzdem, Danke.

Hat das andere Skalieren nun einen Unterschied gemacht?

Es gibt einen berühmten Artikel zu getScaledInstance der leider nur noch in der Wayback machine verfügbar ist: The Perils of Image.getScaledInstance() | Java.net

Aber die andere Skaliermethode sollte diese Nachteile nicht haben.

Hallo Marco13, kannst du mir vielleicht eine lauffähige (druckbare) Version deines Codes zeigen?

Ich kann zwar etwas ausdrucken. Die Ränder würden passen, aber das Bild ist sehr pixelig.

Den getScaledInstance-Aufruf durch die eigene Funktion ersetzt (und ein lokaler Pfad zum Bild):

package bytewelt;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class PrintA4Test
{
    public static double imageableX;
    public static double imageableY;
    public static double imageableWidth;
    public static double imageableHeight;

    public static void main(String[] args) throws IOException
    {
        BufferedImage image = ImageIO.read(new File("./image2.png"));

        PrinterJob pj = PrinterJob.getPrinterJob();
        if (pj.printDialog())
        {
            PageFormat pf = pj.defaultPage();
            Paper paper = pf.getPaper();

            imageableX = 0;
            imageableY = 0;
            imageableWidth = pf.getWidth() - (2 * imageableX);
            imageableHeight = pf.getHeight() - (2 * imageableY);
            paper.setImageableArea(imageableX, imageableY, imageableWidth,
                imageableHeight);

            pf.setOrientation(PageFormat.LANDSCAPE);
            pf.setPaper(paper);
            PageFormat validatePage = pj.validatePage(pf);
            pj.setPrintable(new TestPrintable(image), validatePage);

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

    public static class TestPrintable implements Printable
    {
        private BufferedImage image;

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

        @Override
        public int print(Graphics g, PageFormat pf, int page)
            throws PrinterException
        {
            Graphics2D g2d = (Graphics2D) g.create();
            if (page != 0)
            {
                return NO_SUCH_PAGE;
            }

            g2d.drawImage(getScaledInstance(image, 
                (int) imageableHeight, (int) imageableWidth),
                (int) imageableX, (int) imageableY, null);

            g2d.dispose();
            return PAGE_EXISTS;

        }
    }

    public static BufferedImage getScaledInstance(
        BufferedImage image, int w, int h)
    {
        BufferedImage i = image;
        if (image.getType() != BufferedImage.TYPE_INT_ARGB)
        {
            i = convert(image, BufferedImage.TYPE_INT_ARGB);
        }
        RenderingHints renderingHints = new RenderingHints(null);
        renderingHints.put(RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        return scaleWithAffineTransformOp(i, w, h, null);
    }

    private static BufferedImage convert(BufferedImage image, int type)
    {
        BufferedImage newImage =
            new BufferedImage(image.getWidth(), image.getHeight(), type);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }

    private static BufferedImage scaleWithAffineTransformOp(BufferedImage image,
        int w, int h, RenderingHints renderingHints)
    {
        BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
        double scaleX = (double) w / image.getWidth();
        double scaleY = (double) h / image.getHeight();
        AffineTransform affineTransform =
            AffineTransform.getScaleInstance(scaleX, scaleY);
        AffineTransformOp affineTransformOp =
            new AffineTransformOp(affineTransform, renderingHints);
        return affineTransformOp.filter(image, scaledImage);
    }

}

Ich habe jetzt die lauffähige Version ausprobiert. Die Ränder würden passen. Allerdings ist das Bild sehr pixelig.
Meine Version oben liefert ein besseres Druckbild.

Ich hab’ das jetzt nur mit „Print to PDF“ ausprobiert. Links ist das Bild, vergrößert, und rechts das PDF, was durch das Drucken entsteht, auch vergrößert:

Das Bild selbst ist dort „1:1“ gedruckt (d.h. ohne irgendeine Skalierung). Anscheinent haut die Konvertierung in PDF da irgendeine absurd-harte JPG-Komprimierung drüber.

D.h. das ganze ist jetzt etwas schwierig lokal zu testen. Es gibt noch andere Skalierungsoptionen, und man könnte an dem Graphics2D rumspielen (und dort z.B. andere rendering hints setzen). Aber ohne es testen zu können, wäre das erstmal viel Rumgerate…

Ich glaube, Java bekommt das nicht besser hin …

Original:

PDF:

(ja, es ist körniger / Artefaktbildung …)

Snippet:

import javax.imageio.ImageIO;
import javax.print.PrintService;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.ResolutionSyntax;
import javax.print.attribute.standard.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.print.*;
import java.io.File;

import static java.awt.print.Printable.NO_SUCH_PAGE;
import static java.awt.print.Printable.PAGE_EXISTS;

public class Print {
    public static void main(String[] args) {
        System.out.println(print("radio1.png", "Microsoft Print to PDF"));
    }

    public static boolean print(String fileName, String printerName) {
        PrintService[] services = PrinterJob.lookupPrintServices();
        for (PrintService service : services) {
            System.out.println(service.getName());
            if (service.getName().equalsIgnoreCase(printerName)) {
                try {
                    PrinterJob job = PrinterJob.getPrinterJob();

                    HashPrintRequestAttributeSet set = new HashPrintRequestAttributeSet();
                    set.add(PrintQuality.HIGH);
                    set.add(MediaSize.findMedia(8.27f, 11.67f, MediaSize.INCH));
                    set.add(new MediaPrintableArea(0f, 0f, 8.27f, 11.67f, MediaPrintableArea.INCH));
                    set.add(new PrinterResolution(600, 600, ResolutionSyntax.DPI));
                    set.add(OrientationRequested.PORTRAIT);

                    final BufferedImage img1 = ImageIO.read(new File(fileName));

                    job.setPrintable((graphics, pageFormat, pageIndex) -> {
                        System.out.println("print called: " + pageIndex);
                        if (pageIndex > 0) {
                            return NO_SUCH_PAGE;
                        }
                        double scaleFactor = getScaleFactor(img1);
                        AffineTransform at = new AffineTransform();
                        at.scale(scaleFactor, scaleFactor);
                        AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
                        BufferedImage img2 = new BufferedImage(img1.getWidth(), img1.getHeight(), BufferedImage.TYPE_INT_ARGB);
                        img2 = scaleOp.filter(img1, img2);
                        Graphics2D g = (Graphics2D) graphics;
                        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                        g.drawImage(img2, 0, 0, img2.getWidth(), img2.getHeight(), null);
                        return PAGE_EXISTS;
                    });

                    job.setPrintService(service);
                    job.print(set);
                    return true;
                } catch (Exception e) {
                    System.out.println("Error printing: " + e.getMessage());
                }
            }
        }
        return false;
    }

    public static double getScaleFactor(BufferedImage img) {
        int newWidth = (int) (8.27 * 72); // A4 width in points
        int newHeight = (int) (11.67 * 72); // A4 height in points
        double scaleX = (double) (newWidth) / img.getWidth();
        double scaleY = (double) (newHeight) / img.getHeight();
        return Math.min(scaleX, scaleY);
    }
}