1

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

    }
}
Community
  • 1
  • 1
NoDataDumpNoContribution
  • 10,591
  • 9
  • 64
  • 104

1 Answers1

4

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();
                }
            }
        });
    }
}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • thanks for the code, I will test it and then report back. it requires a bit rethinking on my side because the clip is on the canvas side, not on the image side. – NoDataDumpNoContribution Apr 25 '13 at 08:03
  • I tested it and it works, however using a clip slows down painting. I could not reliably detect if painting outside the clipping area has additional costs. Marked as solution. – NoDataDumpNoContribution Apr 30 '13 at 11:01