简体   繁体   中英

Java2D graphics inside JScrollPanel don't come out well

i am a student of Computer Sciences in the Netherlands and i am working on an assignment in which i have to draw a couple of shapes. To make these shapes look better i set the render hints antialiasing on. But now my problem is that when i scroll with my JScrollPanel to where the shapes are not on the screen and then scroll back it seems that the antialiasing and transparacy aren't working correctly.

In the image linked below i have scrolled to halfway the shape and then scrolled back.

Link to image

I really have no good idea about where the problem resides so i have provided the full code off the class below.

You can download the full eclipse project folder here.

I'd like to thank you in advance,

Milan v. Dijck

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.imageio.ImageIO;
import javax.swing.*;

@SuppressWarnings("serial")

public class AgendaPanel extends JPanel{
    private final int WIDTH = 24; // width in hours
    private final Color BUTTON_TOP_GRADIENT = new Color(250, 250, 250);
    private final Color BUTTON_BOTTOM_GRADIENT = new Color(210, 210, 210);

    private ArrayList<Line> lines = new ArrayList<Line>();
    private ArrayList<Shape> shadowBoxes = new ArrayList<Shape>();
    private ArrayList<Shape> itemBoxes = new ArrayList<Shape>();
    private ArrayList<Image2D> images = new ArrayList<Image2D>();
    private ArrayList<String2D> names = new ArrayList<String2D>();

    public AgendaPanel(){
        setPreferredSize(new Dimension(WIDTH*100, 300));

        drawGrid();
    }

    public void drawGrid(){
        for(int i=100; i < WIDTH*100; i+=100){
            lines.add(new Line(i,0,i,300,Color.GRAY));
        }

        for(int i=50; i < WIDTH*100; i+=100){
            lines.add(new Line(i,0,i,300, Color.LIGHT_GRAY));
        }

        // change to the amount of podia
        for(int i=75; i < 300; i+=75){
            lines.add(new Line(0, i, WIDTH*100, i, Color.black));
        }

        repaint();
    }

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

        RenderingHints qualityHints = new RenderingHints(
                  RenderingHints.KEY_ANTIALIASING,
                  RenderingHints.VALUE_ANTIALIAS_ON );

        qualityHints.put(
                  RenderingHints.KEY_RENDERING,
                  RenderingHints.VALUE_RENDER_QUALITY );

        g2.setRenderingHints( qualityHints );

        // Add agenda items to the draw queue
        addAgendaItem("logo.png", "Minus Militia", 2, 2, 170);

        //draw time grid
        for(Line line : lines){
            g2.setColor(line.color);
            g2.drawLine(line.x1, line.y1, line.x2, line.y2);
        }

        //Shape roundRect = new RoundRectangle2D.Double(2, 2, 170, 71, 20, 20);

        //Paint all shadow shapes as the first layer over the grid with a transparacy of 30%
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.3f));

        for (Shape shadow : shadowBoxes){
            paintBorderShadow(g2,4,shadow);
        }
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,1f));

        //Paint all boxes as second layer over the shadows
        g2.setStroke(new BasicStroke());
        g2.setPaint(new GradientPaint(  new Point(0, 0),
                BUTTON_TOP_GRADIENT,
                new Point(0, 71),
                BUTTON_BOTTOM_GRADIENT));

        for (Shape box : itemBoxes){
            g2.fill(box);
        }

        // Paint as third layer all the images over the boxes
        for (Image2D image : images){
            g2.drawImage(image.getImage() , image.getX(), image.getY(), null);
        }

        g2.setColor(Color.black);

        //Paint all names as fourth and last layer
        for (String2D name : names){
            g2.drawString(name.getText(), name.getX(), name.getY());
        }

    }

    private void addAgendaItem(String imageFile,String name, int x, int y, int length){

        shadowBoxes.add(new RoundRectangle2D.Double(x+3, y+3, length-3, 68, 20, 20));
        itemBoxes.add(new RoundRectangle2D.Double(x, y, length, 71, 20, 20));

        BufferedImage in = null;

        try {
            in = ImageIO.read(new File("logo.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        images.add(new Image2D(x+8, y+8 ,drawPicture(scaleImage(in, 55, 55,Color.black),20)));

        names.add(new String2D(x+73,y+38, name));

    }

    private BufferedImage drawPicture(BufferedImage image, int cornerRadius){
            int w = image.getWidth();
            int h = image.getHeight();
            BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

            Graphics2D g2 = output.createGraphics();

            g2.setComposite(AlphaComposite.Src);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setColor(Color.WHITE);
            g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));

            g2.setComposite(AlphaComposite.SrcAtop);
            g2.drawImage(image, 0, 0, null);

            g2.dispose();

            return output;
    }

    public BufferedImage scaleImage(BufferedImage img, int width, int height, Color background) {
        int imgWidth = img.getWidth();
        int imgHeight = img.getHeight();

        if (imgWidth*height < imgHeight*width) {
            width = imgWidth*height/imgHeight;
        } else {
            height = imgHeight*width/imgWidth;
        }
        BufferedImage output = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = output.createGraphics();
        try {
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g.setBackground(background);
            g.clearRect(0, 0, width, height);
            g.drawImage(img, 0, 0, width, height, null);
        } finally {
            g.dispose();
        }
        return output;
    }

    private void paintBorderShadow(Graphics2D g2, int shadowWidth, Shape shape) {

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        int sw = shadowWidth*2;
        for (int i=sw; i >= 2; i-=2) {
            float pct = (float)(sw - i) / (sw - 1);
            g2.setColor(getMixedColor(Color.DARK_GRAY, pct,
                                      Color.DARK_GRAY, 1.0f-pct));
            g2.setStroke(new BasicStroke(i));
            g2.draw(shape);
        }
    }

    private static Color getMixedColor(Color c1, float pct1, Color c2, float pct2) {
        float[] clr1 = c1.getComponents(null);
        float[] clr2 = c2.getComponents(null);
        for (int i = 0; i < clr1.length; i++) {
            clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2);
        }
        return new Color(clr1[0], clr1[1], clr1[2], clr1[3]);
    }

}

Image2D, String2D and Line classes:

public class String2D {
    private int x = 0;
    private int y = 0;
    private String text = "";

    public String2D(int x, int y, String text){
        this.x = x;
        this.y = y;
        this.text = text;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

import java.awt.image.BufferedImage;


public class Image2D {
    private int x = 0;
    private int y = 0;
    private BufferedImage image = null;

    public Image2D(int x, int y, BufferedImage image){
        this.x = x;
        this.y = y;
        this.image = image;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }
}
import java.awt.*;

public class Line{
    public int x1;
    public int x2;
    public int y1;
    public int y2;
    public Color color;

    public Line(int x1, int y1, int x2, int y2, Color color){
        this.x1 = x1;
        this.x2 = x2;
        this.y1 = y1;
        this.y2 = y2;
        this.color = color;
    }
}

After a personal eureka moment i found out what my problem was. I am resizing and giving images rounded corners every repaint this changed the g2 object somehow disabling my renderhints. I have put this code into the constructor and now everythng runs smooth.

The code below was the problem:

    private BufferedImage drawPicture(BufferedImage image, int cornerRadius){
    int w = image.getWidth();
    int h = image.getHeight();
    BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2 = output.createGraphics();

    g2.setComposite(AlphaComposite.Src);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(Color.WHITE);
    g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));

    g2.setComposite(AlphaComposite.SrcAtop);
    g2.drawImage(image, 0, 0, null);

    g2.dispose();

    return output;
    }

    public BufferedImage scaleImage(BufferedImage img, int width, int height, Color background) {
int imgWidth = img.getWidth();
int imgHeight = img.getHeight();

if (imgWidth*height < imgHeight*width) {
    width = imgWidth*height/imgHeight;
} else {
    height = imgHeight*width/imgWidth;
}
BufferedImage output = new BufferedImage(width, height,
        BufferedImage.TYPE_INT_RGB);
Graphics2D g = output.createGraphics();
try {
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    g.setBackground(background);
    g.clearRect(0, 0, width, height);
    g.drawImage(img, 0, 0, width, height, null);
} finally {
    g.dispose();
}
return output;
}

I called this in the paintcomponent routine:

addAgendaItem("logo.png", "Minus Militia", 2, 2, 170);

Which is this method:

private void addAgendaItem(String imageFile,String name, int x, int y, int length){
shadowBoxes.add(new RoundRectangle2D.Double(x+3, y+3, length-3, 68, 20, 20));
itemBoxes.add(new RoundRectangle2D.Double(x, y, length, 71, 20, 20));

BufferedImage in = null;

try {
    in = ImageIO.read(new File("logo.png"));
} catch (IOException e) {
    e.printStackTrace();
}

images.add(new Image2D(x+8, y+8 ,drawPicture(scaleImage(in, 55, 55,Color.black),20)));

names.add(new String2D(x+73,y+38, name));

}

And this calls the above two methods (every repaint) what resulted in my performance and drawing problems.

This is one way to go, and it might at times be more CPU efficient (as well as more memory consuming), however, this is going around the problem.

The problem exists because you're changing the state of the Graphics object provided as parameter to the 'paintComponent' method. This is a common mistake, which causes various weird looking graphical bugs.

The reason you shouldn't change the state of the object, is because this object is being passed to your component by its parent component, which got it from its parent component, and so forth. Every component in the chain, right down to the owning window uses the same Graphics object to paint itself. The same Graphics object will also be passed automatically to any children of your component. This is the basis of how Swing works (a discussion on how Swing works compared to AWT or even SWT is a different topic altogether).

What you should do instead, is to preserve the state. This can be done in one of two ways:

  1. Before the method returns (ie the last lines of code in the method), set everything you changed back to the way it was (in your case the rendering hints, etc.). This is a good way to go if you have only a few changes to the Graphics object's state.
  2. Create a copy of the Graphics object at the beginning of the method using its 'create' method, use it instead of the original object to paint, and get rid of it at the end of the method by using its 'dispose' method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM