Save JPanel as Image HD Quality

ε祈祈猫儿з 提交于 2019-11-29 18:49:21
MadProgrammer

Let's start with the obvious...

  • A A4 sheet of paper is 21.0cm x 29.7cm.
  • At 300dpi, that makes it 2480.315x3507.874 pixels
  • At 72dpi, that makes it 595.2756x841.8898 pixels

Why's this important? Java renders to the screen at 72dpi, but is capable of printing at 300dpi (above and below, but this is nice figure). This means, roughly, you'd to scale the screen image UP by 4 times. Up scaling is never pleasant, as demonstrated here

A better solution would be work with the printer DPI and scale the image down to the screen.

For convenience sake, you can use something like...

public static final float CM_PER_INCH = 0.393700787f;

public static float cmsToPixel(float cms, float dpi) {
    return cmToInches(cms) * dpi;
}

public static float cmToInches(float cms) {
    return cms * CM_PER_INCH;
}

To convert from cm's to pixels at a given DPI.

Now to the fun part. You "could" use Swing components to render the basic layout, it might be easier, but you'd have to scale the graphics down, as the core printer API assumes a DPI of 72 (don't ask). Now scaling down generally results in a better output, but there is another solution.

Instead, you could use the Graphics 2D API and generate the output yourself...

public static class Ticket {

    public enum TextAlignment {

        LEFT,
        RIGHT,
        CENTRE
    }

    protected static final int STUB_NUMBER_Y_POS = 12;

    private Font plainFont;
    private Font boldFont;

    private Stroke dashedStroke;

    public void paint(Graphics2D g2d, double pageWidth, double pageHeight, int stubNumber) {
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

        paintLeftStub((Graphics2D) g2d.create(), pageWidth, pageHeight, stubNumber);
        paintBody((Graphics2D) g2d.create(), pageWidth, pageHeight, stubNumber);
        paintRightStub((Graphics2D) g2d.create(), pageWidth, pageHeight, stubNumber);
        paintEndStub((Graphics2D) g2d.create(), pageWidth, pageHeight, stubNumber);

        g2d = (Graphics2D) g2d.create();
        g2d.setColor(Color.GRAY);
        g2d.setStroke(getDashedStroke());
        g2d.draw(new Line2D.Double(0, pageHeight - 1, pageWidth, pageHeight - 1));
        g2d.dispose();

    }

    protected void paintLeftStub(Graphics2D graphics, double pageWidth, double pageHeight, int stubNumber) {

        graphics.setColor(Color.BLACK);
        double stubWidth = pageWidth / 4;

        Graphics2D g2d = (Graphics2D) graphics.create();

        Font font = getBoldFont().deriveFont(18f);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();

        // Did mention I hate doing inline transformations :P
        g2d.rotate(Math.toRadians(-90), stubWidth / 2, pageHeight / 2);
        g2d.translate(0, -((stubWidth - pageHeight) / 2));

        String lines[] = {"MORATUFIESTA", "", "Sat. 3 Auguest 2015", "Adult - $3"};
        double x = 2;
        double y = 0;
        double maxWidth = 0;
        for (String text : lines) {

            x = calculateHorizontalCenterPositionFor(text, fm, stubWidth);
            maxWidth = Math.max(maxWidth, fm.stringWidth(text));
            g2d.drawString(text, (int) Math.round(x), (int) Math.round(y + fm.getAscent()));
            y += fm.getHeight();

        }
        double blockWidth = y;

        // Easier then trying to undo the transformation...
        g2d.dispose();
        g2d = (Graphics2D) graphics.create();

        String text = "Low";

        font = getPlainFont().deriveFont(6f);
        g2d.setFont(font);
        fm = g2d.getFontMetrics();
        double xPos = calculateHorizontalCenterPositionFor(text, fm, blockWidth) + 2;
        double yPos = (pageHeight - maxWidth) / 2;
        g2d.drawString(text, (int) Math.round(xPos), (int) Math.round(yPos - fm.getAscent()));
        g2d.setStroke(getDashedStroke());
        g2d.draw(new Line2D.Double(stubWidth, 0, stubWidth, pageHeight));
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        drawStubNumber(g2d, stubWidth - 8 - fm.getHeight() - fm.getAscent(), STUB_NUMBER_Y_POS, stubNumber);
        g2d.dispose();

    }

    protected Stroke getDashedStroke() {

        if (dashedStroke == null) {

            float dash[] = {10.0f};
            dashedStroke = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10f, dash, 0.0f);

        }

        return dashedStroke;

    }

    public Font getPlainFont() {

        if (plainFont == null) {

            plainFont = UIManager.getFont("Label.font");

        }

        return plainFont;

    }

    public Font getBoldFont() {

        if (boldFont == null) {

            boldFont = getPlainFont();
            boldFont = boldFont.deriveFont(Font.BOLD);

        }

        return boldFont;

    }

    protected double calculateHorizontalCenterPositionFor(String text, FontMetrics fm, double width) {
        return (width - fm.stringWidth(text)) / 2d;
    }

    protected void paintBody(Graphics2D graphics, double pageWidth, double pageHeight, int stubNumber) {

        int padding = 8;
        double xOffset = pageWidth / 4d;

        graphics.setColor(Color.BLACK);

        double bodyWidth = (pageWidth / 2d);
        Graphics2D g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        g2d.setFont(getPlainFont().deriveFont(12f));
        FontMetrics fm = g2d.getFontMetrics();
        String text = "Moratu Fiesta";
        double xPos = bodyWidth - fm.stringWidth(text) - padding;
        double yPos = padding;

        g2d.drawString(text, (int) Math.round(xPos), (int) Math.round(yPos + fm.getAscent()));
        g2d.dispose();

        Font plainFont = getPlainFont().deriveFont(9.5f);
        TextLine[] addressLines01 = new TextLine[]{
            new TextLine("61 Railway Pde North", plainFont),
            new TextLine("Glen Waverley", plainFont),
            new TextLine("03 9836 8673", plainFont)
        };
        TextLine[] addressLines02 = new TextLine[]{
            new TextLine("1120, Glen Huntly", plainFont),
            new TextLine("Glen Huntly", plainFont),
            new TextLine("03 9571 5544", plainFont)
        };
        TextLine[] sponsorLines = new TextLine[]{
            new TextLine("Proudly supported by", plainFont),
            new TextLine("Quality Groceries", plainFont.deriveFont(Font.BOLD)),
            new TextLine("Visit for all your grocery needs", plainFont)
        };

        Area area = new Area();
        addTo(g2d, area, addressLines01);
        addTo(g2d, area, addressLines02);
        addTo(g2d, area, sponsorLines);
        int height = area.getBounds().height;
        double bottomBlockYPos = pageHeight - height - padding;

        g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        drawTextLines(g2d, padding, bottomBlockYPos, bodyWidth - (padding * 2), TextAlignment.LEFT, addressLines01);
        drawTextLines(g2d, padding, bottomBlockYPos, bodyWidth - (padding * 2), TextAlignment.CENTRE, sponsorLines);
        drawTextLines(g2d, padding, bottomBlockYPos, bodyWidth - (padding * 2), TextAlignment.RIGHT, addressLines02);
        g2d.dispose();

        plainFont = getPlainFont().deriveFont(10f);
        TextLine[] textLines = new TextLine[]{
            new TextLine("On Saturday, August 3, 2013 from 6.30pm till midnight", plainFont.deriveFont(Font.BOLD)),
            new TextLine("At 21, Sacred Heart Parish Hall, Johnson Street, Oakleigh", plainFont)
        };
        int blockHeight = getSizeFor(g2d, textLines).height;
        double mainBlockYPos = (pageHeight - blockHeight) / 2;

        g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        drawTextLines(g2d, 0, mainBlockYPos, bodyWidth, TextAlignment.CENTRE, textLines);
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        Font boldFont = getBoldFont().deriveFont(9f);
        double upperYPos = (mainBlockYPos + blockHeight);
        double middleBlockYPos = upperYPos + ((bottomBlockYPos - upperYPos) / 2) - (g2d.getFontMetrics(boldFont).getHeight() / 2);
        drawTextLines(g2d, padding, middleBlockYPos, bodyWidth - (padding * 2), TextAlignment.LEFT, new TextLine("Melway Ref, 69 FB", boldFont));
        drawTextLines(g2d, padding, middleBlockYPos, bodyWidth - (padding * 2), TextAlignment.CENTRE, new TextLine("Music by REDEMPTION", boldFont));
        drawTextLines(g2d, padding, middleBlockYPos, bodyWidth - (padding * 2), TextAlignment.RIGHT, new TextLine("Donations $30", boldFont));
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        g2d.setStroke(getDashedStroke());
        g2d.draw(new Line2D.Double(bodyWidth, 0, bodyWidth, pageHeight));
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        drawStubNumber(g2d, padding + xOffset, STUB_NUMBER_Y_POS, stubNumber);
        g2d.dispose();

    }

    protected void drawStubNumber(Graphics2D g2d, double x, double y, int stubNumber) {

        Font font = getBoldFont().deriveFont(18f);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();

        String text = Integer.toString(stubNumber);

        g2d.translate(x, y);
        g2d.rotate(Math.toRadians(-90), fm.stringWidth(text) / 2, fm.getHeight() / 2);

        g2d.drawString(text, 0, fm.getAscent());
        g2d.dispose();
    }

    protected void paintRightStub(Graphics2D graphics, double pageWidth, double pageHeight, int stubNumber) {

        graphics.setColor(Color.BLACK);

        int padding = 8;
        double xOffset = (pageWidth / 4d) * 3;
        double stubWidth = (pageWidth / 4d) / 2;

        Graphics2D g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        g2d.setStroke(getDashedStroke());
        g2d.draw(new Line2D.Double(stubWidth, 0, stubWidth, pageHeight));
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        drawStubNumber(g2d, padding + xOffset, STUB_NUMBER_Y_POS, stubNumber);
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        String text = "Cafe Little Hut";
        Font font = getPlainFont().deriveFont(23f);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();

        double x = xOffset + ((stubWidth - fm.stringWidth(text)) / 2d);
        double y = ((pageHeight - fm.getHeight()) / 2d);

        g2d.translate(x, y);
        g2d.rotate(Math.toRadians(-90), fm.stringWidth(text) / 2, fm.getHeight() / 2);

        g2d.drawString(text, 0, fm.getAscent());

        g2d.dispose();

    }

    protected void paintEndStub(Graphics2D graphics, double pageWidth, double pageHeight, int stubNumber) {

        graphics.setColor(Color.BLACK);

        int padding = 8;
        double stubWidth = (pageWidth / 4d) / 2;
        double xOffset = ((pageWidth / 4d) * 3 + stubWidth);

        Graphics2D g2d = (Graphics2D) graphics.create();
        g2d.translate(xOffset, 0);
        g2d.setStroke(getDashedStroke());
        g2d.draw(new Line2D.Double(stubWidth, 0, stubWidth, pageHeight));
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        drawStubNumber(g2d, padding + xOffset, STUB_NUMBER_Y_POS, stubNumber);
        g2d.dispose();

        g2d = (Graphics2D) graphics.create();
        String text = "Entrance";
        Font font = getBoldFont().deriveFont(32f);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();

        double x = xOffset + ((stubWidth - fm.stringWidth(text)) / 2);
        double y = ((pageHeight - fm.getHeight()) / 2);

        g2d.translate(x, y);
        g2d.rotate(Math.toRadians(-90), fm.stringWidth(text) / 2, fm.getHeight() / 2);

        g2d.drawString(text, 0, fm.getAscent());

        g2d.dispose();

    }

    protected Dimension drawTextLines(Graphics2D g2d, double xPos, double yPos, double width, TextAlignment textAlignment, TextLine... textLines) {

        Area area = new Area();

        for (TextLine textLine : textLines) {
            g2d.translate(xPos, yPos);
            Dimension size = textLine.getBounds(g2d);
            textLine.paint(g2d, width, textAlignment);
            area.add(new Area(new Rectangle2D.Double(xPos, yPos, size.width, size.height)));
            g2d.translate(-xPos, -yPos);
            yPos += size.height;
        }

        return area.getBounds().getSize();

    }

    protected void addTo(Graphics2D g2d, Area area, TextLine... textLines) {

        area.add(new Area(new Rectangle(getSizeFor(g2d, textLines))));

    }

    protected Dimension getSizeFor(Graphics2D g2d, TextLine... textLines) {

        int yPos = 0;
        int width = 0;
        for (TextLine textLine : textLines) {
            Dimension size = textLine.getBounds(g2d);
            yPos += size.height;
            width = Math.max(size.width, width);
        }

        return new Dimension(width, yPos);

    }

    protected class TextLine {

        private String text;
        private Font font;

        public TextLine(String text, Font font) {
            this.text = text;
            this.font = font;
        }

        public String getText() {
            return text;
        }

        public Font getFont() {
            return font;
        }

        public Dimension getBounds(Graphics2D g2d) {
            FontMetrics fm = g2d.getFontMetrics(getFont());
            return new Dimension(fm.stringWidth(text), fm.getHeight());
        }

        public void paint(Graphics2D g2d, double width, TextAlignment textAlignment) {
            Dimension bounds = getBounds(g2d);
            FontMetrics fm = g2d.getFontMetrics(getFont());
            g2d.setFont(font);
            double x = 0;
            switch (textAlignment) {
                case CENTRE:
                    x = (width - bounds.width) / 2;
                    break;
                case RIGHT:
                    x = width - bounds.width;
                    break;
            }
            g2d.drawString(getText(), (int) Math.round(x), fm.getAscent());
        }

    }

}

Why would you do that? It's easier to wrap a Printable around and print to a printer and you can also "paint" to a component (or image)

For example...

public class TicketPrintable implements Printable {

    private Ticket ticket;

    public TicketPrintable(Ticket ticket) {
        this.ticket = ticket;
    }

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

            g2d.translate((int) pageFormat.getImageableX(),
                            (int) pageFormat.getImageableY());

            double ticketHeight = height / 4d;
            for (int index = 0; index < 4; index++) {

                ticket.paint(g2d, width, ticketHeight, index + 1);
                g2d.translate(0, ticketHeight);

            }

            result = PAGE_EXISTS;
        }
        return result;
    }

}

And to print it, you would use something like...

PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
aset.add(MediaSizeName.ISO_A4);
aset.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
aset.add(new MediaPrintableArea(0, 0, 210, 297, MediaPrintableArea.MM));

PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(new TicketPrintable(new Ticket()));

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

On the screen it looks like...

On paper it looks like (scaled down for SO)

Now having said that, I would strongly encourage you to learn JasperReports, which makes this all so much simpler...

Updated

So, I was sitting in traffic and thought to myself, I wonder if I can just use scaling to render the Ticket to an image, so I thought I'd give it a try...

The top image is 72dpi, the bottom image is scaled to 300dpi

double pageWidth = cmsToPixel(21.0f, 300f);
double pageHeight = cmsToPixel(29.7f, 300f);
double imageWidth = cmsToPixel(21.0f, 72f);
double imageHeight = cmsToPixel(29.7f, 72f);

double scaleFactor = ImageUtilities.getScaleFactorToFit(
        new Dimension((int) Math.round(imageWidth), (int) Math.round(imageHeight)),
        new Dimension((int) Math.round(pageWidth), (int) Math.round(pageHeight)));

int width = (int) Math.round(pageWidth);
int height = (int) Math.round(pageHeight);

BufferedImage img = new BufferedImage(
        width,
        height,
        BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight()));
g2d.scale(scaleFactor, scaleFactor);

Ticket ticket = new Ticket();
ticket.paint(g2d, img.getWidth() / scaleFactor, (img.getHeight() / scaleFactor) / 4, 1);
g2d.dispose();

try {
    ImageIO.write(img, "png", new File("Ticket.png"));
} catch (IOException ex) {
    ex.printStackTrace();
}

Scale factor algorithms

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;

}

Note: This is only really going to work for text and primitive graphics, you put an image into this and it won't work, the image will be scaled up and look crappy, use demonstrated in the linked answer from before. In that case, you'd have to design the form to render at 300+dpi and scale the image down to 72dpi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!