1

I'm currently working on a little project for school and I'm still at the very beginning. I've just started reading into JFrame and all that stuff, so don't wonder why I may not be so familiar with everything you'll show me.

The goal for now is to have a program that gives out an image and to be able to change every single pixel of that image manually. Thus, I've written the following code:

public class JavaGraphicsTest {
    private static Pixel pixel;

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        frame.setVisible(true);
        pixel = new Pixel(1600, 900);
        frame.getContentPane().add(pixel);
        //pixel.testChange();
    }
}

and:

public class Pixel extends Component {
    private BufferedImage img;
    private int width;
    private int height;
    private Graphics graphics;

    public Pixel(int w, int h) {
        width = w;
        height = h;
    }

    public void create() {
        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        //Set any color for now
        for(int wc = 0; wc < width; wc++) {
            for(int hc = 0; hc < height; hc++) {
                img.setRGB(wc, hc, new Color(0xAAFFBB).getRGB());
            }
        }
    }      

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        graphics = g;
        create();
        update();
    }

    public void update() {
        graphics.drawImage(img, 0, 0, null);
    }

    public void testChange() {
        for(int i = 50; i < 80; i++) {
            for(int  j = 80; j < 120; j++) {
                img.setRGB(i, j, new Color(0xFF8876).getRGB());
            }
            for(int  j = 460; j < 493; j++) {
                img.setRGB(i, j, new Color(0xFF8876).getRGB());
            }
        }
    }
}

Well, the code works so far (after many hours of nasty error spotting xD), but what I want to do now seems not to work so far: I want to call the method "pixel.testChange()" in the main method (it's currently commented). But as far as I have understood how JFrame works, I can't do anything with an object once I've added it to the JFrame. But who should it work then? How can I modify any active object without deleting and re-adding it?

PS: If you don't understand what the testChange method is supposed to do: It should change two blocks of the image to another color, it's basically a test to see if I successfully changed the image.

If you need further information on the project, please ask me :)

Thanks in advance, Julian

Kedar Mhaswade
  • 4,535
  • 2
  • 25
  • 34
  • Hi Julian, and welcome to stackoverflow! :D I would suggest looking into the revalidate() methods. I don't know much about them myself, but as I understand it should do what you want it to do. :) – Ethan Moore May 07 '16 at 00:46
  • @EthanMoore thanks ^^ that seems to be the basic for doing this, but through more error spotting I found out that the problem I'm currently fighting is another one: When I'm calling this line: `img.setRGB(80, 80, new Color(0xFF8876).getRGB());` in the paint() method, it works, but in the testChange() method I always get a NullPointer :( but I have absolutely no clue as to why it is like this ... – Julian Thurner May 07 '16 at 01:21
  • Try changing the `new Color(0xFF8876).getRGB()` to an existing value (just put another 80?) and tell me what happens? @JulianThurner – Ethan Moore May 07 '16 at 01:30
  • 1
    Seems that wasn't the problem ^^' after undoing the storing of the Graphics object, the error didn't occur anymore ... @EthanMoore – Julian Thurner May 07 '16 at 01:55
  • Oh, alright, cool. Glad it worked out ^_^ – Ethan Moore May 07 '16 at 02:12
  • 1
    Don't extend Component. That is an AWT class. When using Swing you can extend `JComponent` or `JPanel`. Also, don't override paint(). In Swing you should be overriding `paintComponent(..)`. – camickr May 07 '16 at 02:13
  • Yeah, thanks as well ^^ @Ethan Moore – Julian Thurner May 07 '16 at 03:02
  • What's the difference between Component and JComponent? @camickr – Julian Thurner May 07 '16 at 03:02
  • The `JComponent` class extends `Component`, so `JComponent` is-a `Component`. But, as it extends its parent class, it is also more. The top-level `JComponent` will always be a "heavy-weight" component (an actual **window** object), where as the child `JComponent`s inside the parent can instead be "light-weight" components, which don't allocate their own windows - they just draw on their parent. Plus `JComponent` adds built-in support for double buffering, borders, & other goodness. It is the difference between a TV and 4k LED TV, but in this case, the price difference is just 1 letter. – AJNeufeld May 07 '16 at 05:11
  • The price difference is just one letter :D Alright, seems legit, I changed it and it still worked without problems xD – Julian Thurner May 07 '16 at 15:34

2 Answers2

4

Don't store the Graphics object. Ever.

public class Pixel extends Component {
    private Graphics graphics;  // <<-- DO NOT DO THIS

    @Override
    public void paint(Graphics g) {
        // ...
        graphics = g;    // <<-- DO NOT DO THIS
        // ...
    }
}

The graphics object may be recreated each time #paint(Graphics g) is called, and can be invalidated, destroyed or corrupted when #paint(Graphics g) exits.

Ditto, do not CREATE images during the #paint(Graphics g) call. This should be done only once, when your Pixel is created.

public class Pixel extends Component {

    @Override
    public void paint(Graphics g) {
        // ...
        create();    // <<-- DO NOT DO THIS, EITHER.
        // ...
    }
}

But you may safely pass the Graphics object to other methods called from paint(Graphics g).

public class Pixel extends Component {
    // ... 

    public Pixel(int w, int h) {
        width = w;
        height = h;
        create();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        update(g);
    }

    private void update(Graphics g) {
        graphics.drawImage(img, 0, 0, null);
    }
}

Ok. To the business at hand -- your #testChange() method. After changing the image, you simply ask Swing to draw your component again, using the #repaint() call.

public class Pixel extends Component {

    public void testChange() {
        int rgb = new Color(0xFF8876).getRGB());  // Cached, for efficiency.

        for(int i = 50; i < 80; i++) {
            for(int  j = 80; j < 120; j++) {
                img.setRGB(i, j, rgb);
            }
            for(int  j = 460; j < 493; j++) {
                img.setRGB(i, j, rgb);
            }
        }

        repaint(); // <<-- Ask Swing to repaint the component.
    }
}

One final note: you really shouldn't change Swing objects except in the Event Dispatching Thread (EDT). An exception is usually allowed when creating the first window, but before it is set visible.

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
    frame.setVisible(true);             // <<-- Window becomes visible here
    pixel = new Pixel(1600, 900);
    frame.getContentPane().add(pixel);  // <<-- DANGEROUS!!
}

Instead, you can restructure the code like this:

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
    pixel = new Pixel(1600, 900);
    frame.getContentPane().add(pixel);  // <<-- Safe - window not visible yet.
    frame.setVisible(true);             // <<-- Window becomes visible here
}

Better, is to actually switch to the EDT, using SwingUtilities.invokeLater(...):

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            JFrame frame = new JFrame();
            frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
            frame.setVisible(true);             // <<-- Window becomes visible here
            pixel = new Pixel(1600, 900);
            frame.getContentPane().add(pixel);  // <<-- Safe - running on EDT.
            pixel.testChange();  // <<-- Also safe - running on EDT
        }
    });
}

Oh, and with Java8, you can get rid of much of the Runnable boiler plate code:

public static void main(String[] args) {
    SwingUtilities.invokeLater( () -> {
        JFrame frame = new JFrame();
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        frame.setVisible(true);             // <<-- Window becomes visible here
        pixel = new Pixel(1600, 900);
        frame.getContentPane().add(pixel);  // <<-- Safe - running on EDT.
        pixel.testChange();  // <<-- Also safe - running on EDT
    });
}
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • This was very helpful! ^^ Seems the problem really was that I stored the Graphics object ... Well, another question then: What is the benefit of using EDT instead of the usual JFrame? – Julian Thurner May 07 '16 at 01:58
  • Those are different things. A `JFrame` is a **window** - a container which holds menus, panels, buttons and other graphical objects. The Event Dispatching Thread (EDT) is **the (_singular!_) thread** Swing uses for manipulating the graphical objects. Your program can create many threads for doing computations, accessing databases, etc., but it should only ever manipulate graphical objects from the EDT. The `SwingUtilities.invokeLater(...)` is just the magic incantation required to switch to the EDT. You are still creating your "usual" `JFrame`; you are merely doing it from a "safe" place. – AJNeufeld May 07 '16 at 04:55
  • Not having run through all the code, but as a general rule, you should call `setVisible` on the frame last otherwise you'll need to call `revalidate` and `repaint` ;) – MadProgrammer May 07 '16 at 06:32
0

At the end of your testChange method (or after calling it) you should call invalidate on the Pixel object, that is a signal to notify the component to be redrawn as it has been changed.

Check also this SO question.

Community
  • 1
  • 1
sanastasiadis
  • 1,182
  • 1
  • 15
  • 23
  • `#invalidate()` should be used when the layout changes. If the layout is already up-to-date, it may skip triggering a repainting of the component. Use `#repaint()` when you want to ensure repainting is done. – AJNeufeld May 07 '16 at 01:26
  • @AJNeufeld you probably refer to `#revalidate()`. I suppose that since graphically represented data have changed, then this is the definition of 'dirty' object, and then the old representation is considered `invalidated`, so, a new 'valid' representation is needed. – sanastasiadis May 07 '16 at 01:37