Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Swing BufferedImage poor quality

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 Small 4pixel dot Big balls

like image 857
ivenhov Avatar asked Jan 28 '26 05:01

ivenhov


2 Answers

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);
    }
}
like image 131
ivenhov Avatar answered Jan 29 '26 19:01

ivenhov


I fixed up your drawing code.

Here's the ugly GUI.

ChartPanel demo

  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);
        }
    }

}
like image 21
Gilbert Le Blanc Avatar answered Jan 29 '26 19:01

Gilbert Le Blanc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!