Java Swing BufferedImage poor quality

六眼飞鱼酱① 提交于 2020-01-13 05:06:08

问题


I'm trying to create a drawing on BufferedImage and then copy in onto JPanel. When I draw directly on JPanel quality of the picture is v.good but when using intermediate BufferedImage quality / resolution is visibly reduced. I've checked that with zoom option from OSX's Accessibility panel. I'm developing on MacBook Pro Retina.

Is there some sort of automated scaling happening? What am I doing wrong with BufferedImage?

Here's the code demonstrating the problem

package com.sample.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class QualityProblem {

private static final double DOT_SIZE = 4;

public static void main(String[] args) {
    JFrame frame = new JFrame("ChartPanel demo");
    frame.setBackground(Color.WHITE);

    // JPanel draw = new DrawingOK();
    JPanel draw = new DrawingUgly();
    draw.setBackground(Color.BLACK);

    frame.getContentPane().add(draw, BorderLayout.CENTER);
    frame.setSize(new Dimension(1200, 900));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    frame.setVisible(true);
}

private static class DrawingOK extends JPanel {

    private static final long serialVersionUID = 1L;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2draw = (Graphics2D) g.create();
        try {
            g2draw.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2draw.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2draw.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

            Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
            g2draw.setColor(Color.YELLOW);
            g2draw.fill(e);
        } finally {
            g2draw.dispose();
        }
    }
}

private static class DrawingUgly extends JPanel {

    private static final long serialVersionUID = 1L;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Dimension size = getParent().getSize();
        BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);

        Graphics2D ig = image.createGraphics();
        ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        ig.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

        Graphics2D g2draw = (Graphics2D) g.create();
        try {
            Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
            ig.setColor(Color.YELLOW);
            ig.fill(e);
            g2draw.drawImage(image, 0, 0, null);
        } finally {
            ig.dispose();
            g2draw.dispose();
        }
    }
}
}

Edited: Added images with 4 pixel dot and 50D both zoomed in. Ugly one comes from BufferedImage copied onto screen's Graphics


回答1:


I fixed up your drawing code.

Here's the ugly GUI.

  1. I moved the sizing of the panel to the panel constructor. Setting the frame size includes the borders. Setting the panel size gives you the drawing area you want.

  2. I moved the black background painting to the paintComponent method. You might as well do all the painting in one place.

  3. I cleaned up your drawing code. You don't need to make a copy of the paintComponent graphics instance to get Graphics2D.

  4. I made the circle bigger so you could see the sharpness. I moved the origin to the center of the circle, and turned the DOT_SIZE into a radius.

Here's the code.

package com.ggl.testing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class QualityProblem implements Runnable {

    private static final double DOT_SIZE = 50D;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new QualityProblem());
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("ChartPanel demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setBackground(Color.WHITE);

        // JPanel draw = new DrawingOK();
        JPanel draw = new DrawingUgly();

        frame.getContentPane().add(draw, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }

    private class DrawingOK extends JPanel {

        private static final long serialVersionUID = 1L;

        public DrawingOK() {
            this.setPreferredSize(new Dimension(600, 400));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2draw = (Graphics2D) g;

            g2draw.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2draw.setRenderingHint(RenderingHints.KEY_RENDERING,
                    RenderingHints.VALUE_RENDER_QUALITY);
            g2draw.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                    RenderingHints.VALUE_STROKE_PURE);

            g2draw.setColor(Color.BLACK);
            g2draw.fillRect(0, 0, getWidth(), getHeight());

            Ellipse2D.Double e = new Ellipse2D.Double(300D - DOT_SIZE,
                    200D - DOT_SIZE, DOT_SIZE + DOT_SIZE, DOT_SIZE + DOT_SIZE);
            g2draw.setColor(Color.YELLOW);
            g2draw.fill(e);
        }
    }

    private class DrawingUgly extends JPanel {

        private static final long serialVersionUID = 1L;

        public DrawingUgly() {
            this.setPreferredSize(new Dimension(600, 400));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            BufferedImage image = new BufferedImage(getWidth(), getHeight(),
                    BufferedImage.TYPE_INT_RGB);

            Graphics2D ig = image.createGraphics();
            ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            ig.setRenderingHint(RenderingHints.KEY_RENDERING,
                    RenderingHints.VALUE_RENDER_QUALITY);
            ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                    RenderingHints.VALUE_STROKE_PURE);

            ig.setColor(Color.BLACK);
            ig.fillRect(0, 0, getWidth(), getHeight());

            Ellipse2D.Double e = new Ellipse2D.Double(300D - DOT_SIZE,
                    200D - DOT_SIZE, DOT_SIZE + DOT_SIZE, DOT_SIZE + DOT_SIZE);
            ig.setColor(Color.YELLOW);
            ig.fill(e);
            ig.dispose();

            g.drawImage(image, 0, 0, this);
        }
    }

}



回答2:


This is simply because in one case, you're drawing on a hardware-supported surface that says it's 640x480 but rendering is done at 2x (or whatever scaling factor of your display) resolution. In the case of BufferedImage you're drawing onto a literal 640x480 pixel buffer. Obviously, that will look worse.




回答3:


I think that the image and panel are using different rendering hints on OS X for hints you've not explicitly set. Copy/paste the textual output of this code back into the question.

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class QualityProblem {

    private static final double DOT_SIZE = 40;

    public static void main(String[] args) {
        JFrame frame = new JFrame("ChartPanel demo");
        frame.setLayout(new GridLayout(0, 1));

        frame.getContentPane().add(new DrawingUgly());
        frame.getContentPane().add(new DrawingOK());
        frame.setSize(new Dimension(400, 300));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.setVisible(true);
    }

    private static class DrawingOK extends JPanel {

        private static final long serialVersionUID = 1L;

        DrawingOK() {
            setBackground(Color.GREEN);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2draw = (Graphics2D) g.create();
            System.out.println("Panel Rendering Hints:");
            printRenderingHints(g2draw);
            try {
                g2draw.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2draw.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2draw.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

                Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
                g2draw.setColor(Color.YELLOW);
                g2draw.fill(e);
            } finally {
                g2draw.dispose();
            }
        }
    }

    private static class DrawingUgly extends JPanel {

        private static final long serialVersionUID = 1L;

        DrawingUgly() {
            setBackground(Color.RED);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Dimension size = getParent().getSize();
            BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);

            Graphics2D ig = image.createGraphics();

            System.out.println("Image Rendering Hints:");
            printRenderingHints(ig);
            ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            ig.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

            Graphics2D g2draw = (Graphics2D) g.create();
            try {
                Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
                ig.setColor(Color.YELLOW);
                ig.fill(e);
                g2draw.drawImage(image, 0, 0, null);
            } finally {
                ig.dispose();
                g2draw.dispose();
            }
        }
    }

    private static void printRenderingHints(Graphics2D g) {
        RenderingHints renderingHints = g.getRenderingHints();
        RenderingHints.Key[] renderHintsKeys = {
            RenderingHints.KEY_ALPHA_INTERPOLATION,
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.KEY_COLOR_RENDERING,
            RenderingHints.KEY_DITHERING,
            RenderingHints.KEY_FRACTIONALMETRICS,
            RenderingHints.KEY_INTERPOLATION,
            RenderingHints.KEY_RENDERING,
            RenderingHints.KEY_STROKE_CONTROL,
            RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.KEY_TEXT_LCD_CONTRAST
        };
        for (RenderingHints.Key key : renderHintsKeys) {
            Object o = renderingHints.get(key);
            String value = o==null ? "null" : o.toString();
            System.out.println(key + " \t" + value);
        }
    }
}

Note that on Windows it produces an identical list of values.




回答4:


HaraldK in one comments below question gave really good advice. BufferedImage size needs to be multiplied by 2, Graphics2D for that image must be set with scale 2 and target Graphics2D (of the screen device) needs to be scaled with 0.5. With those settings both circles look exactly the same when zoomed in. Bellow complete, modified DrawingUgly class.

    private static class DrawingUgly extends JPanel {

    private static final long serialVersionUID = 1L;

    public DrawingUgly() {
        this.setPreferredSize(new Dimension(600, 25));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2draw = (Graphics2D) g.create();

        double scale = 2;
        BufferedImage image = new BufferedImage((int) (getWidth() * scale), (int) (getHeight() * scale), BufferedImage.TYPE_INT_RGB);

        Graphics2D ig = image.createGraphics();
        ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        ig.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

        ig.scale(scale, scale);

        ig.setColor(Color.BLACK);
        ig.fillRect(0, 0, getWidth(), getHeight());

        Ellipse2D.Double e = new Ellipse2D.Double(10, 10, DOT_SIZE, DOT_SIZE);
        ig.setColor(Color.YELLOW);
        ig.fill(e);
        ig.dispose();

        g2draw.scale(1.0d / scale, 1.0d / scale);

        g2draw.drawImage(image, 0, 0, this);
    }
}


来源:https://stackoverflow.com/questions/31393438/java-swing-bufferedimage-poor-quality

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