tags:

views:

128

answers:

2

Somehow the image is messed up when it is scaled to be relatively wider. It only happens on some machine; everything seems fine on a different machine. And it only happens for some images.

Here is the messed up display:

alt text

This is when I make the window (JFrame) a little wide, so it becomes OK:

alt text

(Note I only showed the top part of image, so the grass in the messed-up version is not in the second one)

The complete code to demonstrate this:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;


public class Imager {

 /**
  * @param args
  */
 public static void main(String[] args) {
  JFrame frame = new JFrame();
  ImageComponent component = new ImageComponent("P1010013.JPG");
  frame.add(component);
  frame.setPreferredSize(new Dimension(300, 300));
  frame.pack();
  frame.setVisible(true);
 }

 static class ImageComponent extends JComponent {
  Image img;
  ImageComponent(String file) {
   InputStream inputStream;
   try {
    inputStream = new FileInputStream(file);
    img = ImageIO.read(inputStream);
   } catch (FileNotFoundException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }

  protected void paintComponent(Graphics g) {
   super.paintComponent(g);
   // g.drawImage(img, 0, 0, 100, 100, null);
   g.drawImage(img, 0, 0, 1000, 1000, null);
  }

 }

}
+2  A: 

You need to construct your GUI on the event dispatch thread (EDT).

Addendum 1: Here's a complete example.

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Imager {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new ImagePanel("P1010013.JPG"));
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

    private static class ImagePanel extends JPanel {

        BufferedImage img;

        ImagePanel(String name) {
            super(true);
            try {
                img = ImageIO.read(new File(name));
                this.setPreferredSize(new Dimension(
                    img.getWidth(), img.getHeight()));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), null);
        }
    }
}

Also, Ash is right about scaling. Try g.drawImage(img, 0, 0, null); instead.

Addendum 2: You can determine if you're drawing on the EDT using isEventDispatchThread().

Addendum 3: I've expanded the example above.

Addendum 4: As Ash notes, "If you do not honor the opaque property you will likely see visual artifacts."—JComponent.

trashgod
Isn't ImageIO.read a synchronous call? The frame hasn't been made visible before the read is complete.
Ash
(To put my previous comment another way so that it makes a bit more sense): Hence paintComponent isn't called until after the image is loaded. At least, that's what I'm finding when I run it.
Ash
Thanks for the response. However, when I changes the width the frame, the image consistently change back and forth between normal and 'scrambled'. It is not EDT and synchronous problem.Also, g.drawImage(img, 0, 0, null) does not solve the problem. But if I made the image very small, say g.drawImage(img, 0, 0, 100, 100, null); the problem will not appear.
@Ash: you're right. @zhou.ji: Drawing from a non-EDT thread is unreliable; it's worth checking. Rather than a screenshot, can you link to the original image?
trashgod
Thanks again for trying to help. There is no "drawing from a non-EDT thread" here if you have a better understanding of what EDT is.Just to avoid further distraction, EDT really starts at calling frame.setVisible(true) and there is nothing after that in my demo program. Everything afterward (all my scaling window stuff) all happens in EDT.
I saw a comment about paintChildren and couldn't find it any more. Ok, that is purely a typo. It should be paintComponent. I fixed it in the question. It doesn't affect the problem I asked about. Again, the code is far from a real Swing app, just to demonstrate the problem. It is only reproducible on one of my two machines. No answer seems to be close to he point yet.
Because we can't reproduce the problem...
Ash
@zhou.ji: I believe you are mistaken about the EDT. Variability among platforms is typical of painting from the initial thread. For reference, I've included a complete example.
trashgod
@trashgod: in my original application, GUI is not initialized in the main thread, just as in your example. Here I just want to create a simplest code to reproduce the problem. I did test your way and it made no difference in the problematic behavior.@Ash: I could not reproduce it on a different machine either. Just hope somebody experienced similar issue before.
@zhou.ji: Try extending JPanel instead of JComponent. JComponent is non-opaque by default, hence the background will not be painted by the default rendering algorithm. That might have been why you were seeing artifacts sometimes...
Ash
@Ash: Very insightful, and the more likely cause. Consider adding this as an answer; I'd be pleased to refer to it in mine. I used JPanel in my example merely out of habit.
trashgod
+2  A: 

(Moved from comments): Extending a JComponent could be part of the problem. Since a JComponent is non-opaque by default, the background won't be cleared as part of the repaint process. Using a JPanel (opaque by default, but otherwise fairly similar to a JComponent) could fix the problem.

Ash