My program draws numerous rectangles in a JPanel; the location, dimensions and colors are determined by a random number generator. I want to save the panel contents as a jpg. I believe this requires converting the panel contents to a buffered image using something like this:

BufferedImage awtImage = new BufferedImage(canvasPanelWidth,canvasPanelHeight,BufferedImage.TYPE_INT_RGB);  
Graphics2D g2 = awtImage.createGraphics();
canvasPanel.print(g2);

This apparently causes my paintComponent method to execute, which generates a new set of randome numbers and a completely new set of rectangles and associated jpg file.

Is there another way to save the JPanel as a jpg that will not cause a repaint or do I have to generate the rectangles directly in a buffered image and display in a JLabel as an imageIcon?

Thanks for any suggestions.

Dani AI

Generated

As pointed out, the correct pattern is to separate the model (the random-rectangle data) from the view (paintComponent) and render once into an offscreen image. Generating new random values inside paintComponent guarantees a different image whenever the component is asked to paint (printing, resizing, exposure, etc.). The practical fix is: create and keep a BufferedImage (the backing image) when the rectangle set is generated, draw the rectangles into that image, then have paintComponent simply draw that image. When saving, write that backing image to disk (JPEG requires an RGB image without alpha).

Example of the workflow (fields and generation step):

// class fields:
BufferedImage backing;
List<RectSpec> rects; // each RectSpec holds x,y,w,h,Color

// generate rectangles once (on program logic or background thread), then create backing:
int w = panel.getWidth(), h = panel.getHeight();
BufferedImage tmp = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = tmp.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0,0,w,h);
for (RectSpec r : rects) {
  g.setColor(r.color);
  g.fillRect(r.x, r.y, r.w, r.h);
}
g.dispose();
backing = tmp;
panel.repaint();

Keep paintComponent trivial and save from the backing:

@Override
protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  if (backing != null) g.drawImage(backing, 0, 0, this);
}

// save as JPG (JPEG doesn't support alpha):
BufferedImage rgb = new BufferedImage(backing.getWidth(), backing.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = rgb.createGraphics();
g2.drawImage(backing, 0, 0, Color.WHITE, null);
g2.dispose();
ImageIO.write(rgb, "jpg", new File("out.jpg"));

Notes and cautions: recreate the backing when the panel size changes; perform expensive generation or ImageIO.write off the Event Dispatch Thread (SwingWorker) so the UI stays responsive; choose PNG if transparency is needed; use ImageWriter/ImageWriteParam for custom JPEG quality. This keeps painting deterministic and ensures the saved file matches what was displayed.

Recommended Answers

All 2 Replies

Instead of drawing directly to the panel's Graphics, create a new Image and draw to its Graphics. Then just copy that to the panel using drawImage and also save it as a jpeg

ps: Updating the random numbers inside paintComponent is a really bad idea. You have no direct control over when it is called, and it may be called when you don't expect, eg when the user re-sizes the window, or drags it onto a second monitor, or various other reasons that you have no access to.
You have to update them only when your program logic explicitly requires a new set of rectangles. That would also be the best time to create your own Image with those new rectangles, so your paintComponent just has to draw the latest Image to its own Graphics

Thank you. I'll implement as you have suggested.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.