Search code examples
javaimageswingmasking

How to efficiently draw a subpart of an image in Swing


Let's say I have a BufferedImage of type TYPE_4BYTE_ABGR in Swing and I want to draw only a part of it. For example I would like to draw the left half only or some triangular shape or something more complicated.

Reason is that the final image shall be composed from subparts of individual images I have.

What's the best way to do that?

I would prefer to define a polygon and then use this shape as a mask for drawing, if this is possible.

My current idea: make a copy of the individual image and set all pixels outside the wished shape to transparent, then draw the whole image. I think this might work but might be too slow with the copying and all.

edit:

I tested the solution of Guillaume and found that it works and does not extremely slow down the painting. Using a clip resulted in an increase of drawing time from 14ms to 35ms but these times are very inaccurate. I used profiling the EDT from here. Here is the code.

import java.awt.AWTEvent;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 *
 */
public class ClipTilesTest {

    // tile size and number of tiles in each row/column
    private static int TILE_SIZE = 100;
    private static int TILE_NUM = 6;

    // taken from https://stackoverflow.com/questions/5541493/how-do-i-profile-the-edt-in-java-swing
    public static class TimedEventQueue extends EventQueue {

        @Override
        protected void dispatchEvent(AWTEvent event) {
            long startNano = System.nanoTime();
            super.dispatchEvent(event);
            long endNano = System.nanoTime();

            if (endNano - startNano > 5000000) {
                System.out.println(((endNano - startNano) / 1000000) + "ms : " + event);
            }
        }
    }

    private static void initUI() {

        Toolkit.getDefaultToolkit().getSystemEventQueue().push(new TimedEventQueue());

        // download image
        BufferedImage image;
        try {
            image = ImageIO.read(new URL("http://download.chip.eu//ii/163859211_4b28e1e687.jpg"));
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        // take out small chunk
        final BufferedImage tile = image.getSubimage(0, 0, TILE_SIZE, TILE_SIZE);

        JFrame frame = new JFrame();
        frame.setTitle(ClipTilesTest.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        // the panel containing some tiles
        JPanel view = new JPanel() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Graphics2D g2d = (Graphics2D) g;

                for (int i = 0; i < TILE_NUM; i++) {
                    for (int j = 0; j < TILE_NUM; j++) {

                        // version 1
                        /*
                        g2d.setClip(i * TILE_SIZE, j * TILE_SIZE , (i+1)*TILE_SIZE, (j+1)*TILE_SIZE);
                        g2d.drawImage(tile, i * TILE_SIZE, j * TILE_SIZE, null);
                        */

                        // version 2

                        g2d.setClip(i * TILE_SIZE, j * TILE_SIZE , i*TILE_SIZE + TILE_SIZE/2, (j+1)*TILE_SIZE);
                        g2d.drawImage(tile, i * TILE_SIZE, j * TILE_SIZE, null);
                        g2d.setClip(i * TILE_SIZE + TILE_SIZE/2, j * TILE_SIZE , (i+1)*TILE_SIZE , (j+1)*TILE_SIZE);
                        g2d.drawImage(tile, i * TILE_SIZE, j * TILE_SIZE, null);

                    }
                }

            }
        };
        view.setPreferredSize(new Dimension(TILE_SIZE * TILE_NUM, TILE_SIZE * TILE_NUM));

        // add, pack, set visible
        frame.add(view);
        frame.pack();
        frame.setVisible(true);

        // now make a repaint event, so we can start measuring
        view.repaint();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ClipTilesTest.initUI();
            }
        });

    }
}

Solution

  • One easy way to achieve this effect, is to modify the "clip" of the Graphics object and to set it to the shape you want to draw.

    I don't know how efficient this is, but you could consider caching the clipped image and then draw the entire cached image.

    Here is a small demo code:

    result

    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.Rectangle;
    import java.awt.Shape;
    import java.awt.geom.Ellipse2D;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    import javax.swing.ImageIcon;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class TestClippedPanel {
    
        private static class ClippedPanel extends JPanel {
    
            private ImageIcon image;
    
            private List<Shape> shapes;
    
            public ClippedPanel() throws MalformedURLException {
                shapes = new ArrayList<Shape>();
                image = new ImageIcon(new URL("http://download.chip.eu//ii/163859211_4b28e1e687.jpg"));
                Random random = new Random();
                for (int i = 0; i < 10; i++) {
                    int x = random.nextInt(image.getIconWidth() - 1);
                    int y = random.nextInt(image.getIconHeight() - 1);
                    int w = random.nextInt(image.getIconWidth() - x) + 1;
                    int h = random.nextInt(image.getIconHeight() - y) + 1;
                    shapes.add(new Rectangle(x, y, w, h));
                }
                for (int i = 0; i < 10; i++) {
                    int x = random.nextInt(image.getIconWidth() - 1);
                    int y = random.nextInt(image.getIconHeight() - 1);
                    int w = random.nextInt(image.getIconWidth() - x) + 1;
                    int h = random.nextInt(image.getIconHeight() - y) + 1;
                    shapes.add(new Ellipse2D.Double(x, y, w, h));
                }
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Image img = image.getImage();
                for (Shape shape : shapes) {
                    ((Graphics2D) g).setClip(shape);
                    g.drawImage(img, 0, 0, this);
                }
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(image.getIconWidth(), image.getIconHeight());
            }
    
        }
    
        protected void initUI() throws MalformedURLException {
            final JFrame frame = new JFrame(TestClippedPanel.class.getSimpleName());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            final ClippedPanel panel = new ClippedPanel();
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        new TestClippedPanel().initUI();
                    } catch (MalformedURLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }
    }