0

I hope you can help me concerning a problem in my drawString.

I do some drawing within a Factory class and return a BufferedImage with a String on it.

public class Factory {

   public BufferedImage create() {
       TempPanel tempPanel = new TempPanel(new Dimension(100, 100));
       return tempPanel.createImage();
   }

   private class TempPanel extends JPanel {

       public TempPanel(Dimension d) {
           this.setVisible(true);
           this.setSize(d);
       }

       public BufferedImage createImage() {
           BufferedImage bi = new BufferedImage(this.getSize().getWidth(), this.getSize().getHeight(), BufferedImage.TRANSLUCENT);
           this.paintComponent(bi.createGraphics());
           return bi;
       }

       @Override
       public void paintComponent(Graphics g) {
           super.paintComponents(g);
           // General setup
           Graphics2D g2 = (Graphics2D) g;
           g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

           // Transparent Background
           g2.setComposite(AlphaComposite.Clear);
           g2.fillRect(0, 0, graphicalObject.width, graphicalObject.height);
           g2.setComposite(AlphaComposite.Src);

           // Different settings for a certain graphical object
           g2.setFont(new Font("TimesRoman", Font.PLAIN, 12);

           // Actual drawing
           g2.drawString("Test", 0, 0);
       }
    }
}

And then I also have a JPanel in which this image should be drawn:

public class Label extends JPanel {
   // ...
   @Override
   public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        Factory factory = new Factory();
        BufferedImage img = factory.create();
        g2.drawImage(img, 0, 0, null);
   }
}

The problem ist that the text only appears if i minimize and restore the window, but as soon as I start moving the mouse within the window, the text disappears again. When I call drawRect() instead of drawString(), everything works fine?!

Can someone tell me why?

Thanks!

  • 1
    1- Make sure you are disposing of any `Graphics` context you are making. 2- Why are you using a `JPanel` and it's `paintComponent` method to render the content onto the `BufferedImage`? The `JPanel` could be making choices about when it should or shouldn't paint based on the fact that it's not displayable? – MadProgrammer Oct 14 '14 at 02:41
  • `"Test"` has no descenders; see this possible [duplicate](http://stackoverflow.com/q/2658554/230513). – trashgod Oct 14 '14 at 02:52

1 Answers1

1

There are a number of things that "weird" me out...

Firstly, you're creating a new Graphics context, but you're not disposing of it. This could be leaving resources open and may actually prevent the contents from been rendered on some OSs...

So instead of...

BufferedImage bi = new BufferedImage(this.getSize().getWidth(), this.getSize().getHeight(), BufferedImage.TRANSLUCENT);
this.paintComponent(bi.createGraphics());

You should be doing something more like...

BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TRANSLUCENT);
Graphics2D g2d = bi.createGraphics();
this.paintComponent(g2d);
g2d.dispose();

Secondly, text is not render the same way that most other elements are, that is, it doesn't start at x/y and render down/right, in most cases it starts at x/y and renders, slightly, up/right. The actual coordinate is based on the Fonts FontMetrics getAscent value...

So, instead of...

            g2.setFont(new Font("TimesRoman", Font.PLAIN, 12));
            // Actual drawing
            g2.drawString("Test", 0, 0);

You should be doing something more like...

            g2.setFont(new Font("TimesRoman", Font.PLAIN, 12));
            FontMetrics fm = g2.getFontMetrics();
            // Actual drawing
            g2.drawString("Test", 0, fm.getAscent());

See Working with Text APIs or more details...

Thirdly, JComponent and by extension, anything that extends from it, is an ImageObsever, this is important, as not everything that is painted is ready for display immediately, sometimes it needs further processing, rather than having to deal with this annoyance yourself, you can delegate the responsibility, so instead of...

g2.drawImage(img, 0, 0, null);

You should be doing something more like...

g2.drawImage(img, 0, 0, this);

This allows the component to listen to the underlying image and respond to any changes that might happen to the image data and update itself.

Forthly...

private class TempPanel extends JPanel {

   public TempPanel(Dimension d) {
       this.setVisible(true);
       this.setSize(d);
   }

   public BufferedImage createImage() {
       BufferedImage bi = new BufferedImage(this.getSize().getWidth(), this.getSize().getHeight(), BufferedImage.TRANSLUCENT);
       this.paintComponent(bi.createGraphics());
       return bi;
   }

   @Override
   public void paintComponent(Graphics g) {
       super.paintComponents(g);
       // General setup
       Graphics2D g2 = (Graphics2D) g;
       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

       // Transparent Background
       g2.setComposite(AlphaComposite.Clear);
       g2.fillRect(0, 0, graphicalObject.width, graphicalObject.height);
       g2.setComposite(AlphaComposite.Src);

       // Different settings for a certain graphical object
       g2.setFont(new Font("TimesRoman", Font.PLAIN, 12);

       // Actual drawing
       g2.drawString("Test", 0, 0);
   }
}

Is freaking me out...Swing components are meant to be attached to a native peer onto which they are rendered, they are also optimised not to paint when they aren't displayable, this could be throwing off your rendering...

Infact, you could achieve the same thing using something like...

public static class Factory {

    private static Renderer renderer;

    public BufferedImage create() {

        if (renderer == null) {
            renderer = new Renderer(new Dimension(100, 100));
        }

        return renderer.createImage();
    }

    private class Renderer {

        private Dimension size;

        public Renderer(Dimension size) {
            this.size = size;
        }

        public BufferedImage createImage() {
            BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TRANSLUCENT);
            Graphics2D g2 = bi.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            // Transparent Background
            g2.setComposite(AlphaComposite.Clear);
            g2.fillRect(0, 0, graphicalObject.width, graphicalObject.height);
            g2.setComposite(AlphaComposite.Src);
            // Different settings for a certain graphical object
            g2.setFont(new Font("TimesRoman", Font.PLAIN, 12));
            FontMetrics fm = g2.getFontMetrics();

            // Actual drawing
            g2.drawString("Test", 0, fm.getAscent());
            g2.dispose();
            return bi;
        }

    }
}

Which would divorce the actual painting from anything else and provide you with control over it...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366