javaimageswingresizejscrollpane

JScrollPane doest not act correctly when image is resized


I searched and found tons of questions here about my problem, but it seems I cannot implement correctly the solutions I found.

I have a JScrollPane in which I put a custom JPanel. This custom JPanel resized an image. This image is put in a JLabel and the JLabel in the custom JPanel.

This is my code:

mainPanel = new PanelPictureV2(gr.getImage());
mainPanel.setBackground(Color.WHITE);
mainPanel.setLayout(new BorderLayout());
    
scrollPane = new JScrollPane(mainPanel);
add(scrollPane, BorderLayout.CENTER);

Here is my custom JPanel

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class PanelPictureV2 extends JPanel implements MouseWheelListener{
    
    private double zoomFactor = 1;
    private double zoomMin = -20;
    private double zoomMax = 20;
    
    private BufferedImage myPicture;
    private BufferedImage newImage;
    
    public PanelPictureV2(BufferedImage myPicture) {
        this.myPicture = myPicture;
        addMouseWheelListener(this);
    }
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        removeAll();
        if(zoomFactor > zoomMax) zoomFactor = zoomMax;
        if(zoomFactor < zoomMin) zoomFactor = zoomMin;
        newImage = resize(myPicture, (int)(myPicture.getWidth() * zoomFactor), (int (myPicture.getHeight() * zoomFactor));
        add(new JLabel(new ImageIcon(newImage)), BorderLayout.CENTER);
        validate();
        repaint();
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
        if (e.isControlDown()) {
            //Zoom in
            if (e.getWheelRotation() < 0) {
                zoomFactor *= 1.1;
                repaint();
            }
            //Zoom out
            if (e.getWheelRotation() > 0) {
                zoomFactor /= 1.1;
                repaint();
            }
        }
    }
    @Override
    public Dimension getPreferredSize() {
        return newImage == null ? new Dimension(200, 200) : new Dimension(newImage.getWidth(), newImage.getHeight());
    }
    
    private BufferedImage resize(BufferedImage myPicture, int width, int height) {
        Image tmp = myPicture.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage dimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        
        Graphics2D g2d = dimg.createGraphics();
        g2d.drawImage(tmp, 0, 0, null);
        g2d.dispose();
        return dimg;
    }
    
    public void setMyPicture(BufferedImage myPicture) {
        this.myPicture = myPicture;
    }

}

But what I got is this: enter image description here

JScrollPane does not work correctly.

I think I am missing somephing about the resizing of the image. Thank you.


Solution

  • Whoa: you've got bad code in the paintComponent here:

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        removeAll(); // ***** here
        if(zoomFactor > zoomMax) zoomFactor = zoomMax;
        if(zoomFactor < zoomMin) zoomFactor = zoomMin;
        
        // **********  all the code below
        newImage = resize(myPicture, (int)(myPicture.getWidth() * zoomFactor), (int (myPicture.getHeight() * zoomFactor));
        add(new JLabel(new ImageIcon(newImage)), BorderLayout.CENTER);
        validate();
        repaint();
        // **********
    }
    

    The paintComponent method is for painting and painting only and should never be used to add or remove or create components or for calling revalidate or repaint.

    Instead, get rid of the JLabel, the adding and removing of images and instead display the resized BufferedImage in the paintCompoent.

    Myself, I would do the zooming and image creating and resizing in the mouse listener, and then in paintComponent simply call after the super's call, g.drawImage(myImage, 0, 0, null);

    Or better still:

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (myPicture != null) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.scale(zoomFactor, zoomFactor);
            g2d.drawImage(myPicture, 0, 0, null);
            g2d.dispose();
        }
    }
    

    Note that I am creating a new Graphics object so that the zoom transformation only affects the image drawing and no other drawing that may be performed down the graphics drawing chain. I also dispose of this same graphics object that I myself create and never the one given by the JVM (which would break the graphics chain).

    Also, of course resize the preferred size based on the image size.