Graphics(image) stops working after I draw image in another Graphics

gui
#1

I create a Graphics for an Image in the constructor of my MainComponent, which extends AnimatedAppComponent:

mImage.reset(new Image(Image::RGB, 200, 200, true));
mImageGraphics.reset(new Graphics(*mImage));
mImageGraphics->fillAll (Colours::blue);

I can draw into the image using mImageGraphics:

mImageGraphics->drawLine(x0, y0, x1, y1);

Then I draw the image into my MainComponent using:

g.drawImageAt(*mImage, x, y);

Everything works fine up to this point. But when I continue drawing into my image none of the new drawing shows up at the end. If I create a new Graphics(image) and draw into that then it works. But I would prefer not to have to reallocate it every time.

It is as if drawing the image into another Graphics makes the first Graphic(image) stop working. Are the Image and the original Graphics getting disconnected somehow?

My goal is to keep an image that accumulates drawing operations over time. When the paint method is called I want to just draw the current image into the window to see the changes. (Currently all of this is happening in one GUI thread but I might do the drawing into the Image in another thread eventually.)

I tried to upload my class code but was not allowed.

#2

You are creating two objects on the heap, that are not supposed to be there:

Image is a lightweight wrapper around a shared ImagePixelData storage. So passing an Image from one point to another is cheap. It is internally reference counted.

If you want to create an independent copy of an image, so you don’t draw accidentally on the original, call createCopy().

The second is the Graphics context. This is again a class to be created on the stack and to be forgotten straight after drawing.

// class member
Image mImage;

// to setup in the constructor just assign an Image
mImage = Image (Image::RGB, 200, 200, true);

// to draw into the Image:
Graphics g (mImage);
g.fillAll (Colours::blue);

// and in paint:
g.drawImageAt (mImage, x, y);

Hope that helps

#3

Thanks. That works. I am a little worried about the construction of a Graphics object every time I draw. But I’ll try it.

I still wonder why my Graphics object is unusable after drawing the Image in another Graphics. An unsolved mystery.

BTW, loving JUCE! So much better than using Objective C on Mac OS.

#4

To clarify on @daniel’s points about the heap:

Both the Image and Graphics classes employ a concept called Resource Acquisition Is Initialisation (RAII). Essentially this means that they perform setup and tear-down functionality (via the class constructors and destructors) any time you create/destroy one of them.

As Daniel mentioned, the Image class is a reference-counting wrapper around some shared pixel data. When you create an image simply by Image myImage; it doesn’t refer to anything, but creating it via Image myImage(/* constructor arguments */); will create new backing pixel data for you.

Copy-assignment of images (i.e. Image someImage = myExistingImage;) simply bumps up the reference count of an underlying pixel buffer, so both instances of the class refer to the same backing data. When all instances of Image are destroyed or are set to a null image, the reference count of their shared underlying data will drop to 0 and it will automatically be de-allocated.

The Graphics object is similar, you initialise it with a given Image and it will be tied to that image for its lifespan.

The reason you normally don’t heap allocate either of these types of objects is because of their RAII properties.

For starters, the Image class doesn’t really hold any data itself besides that shared reference count, so you can simply pass Image objects around as if there were like pointers or references. As long as you’re doing copy-construction/copy-assignment (i.e. Image someImage = myExistingImage;) the backing pixel data will remain “alive”. This means you can put Image objects into arrays and other data structures without actually using the new operator to heap allocate them.

Similarly, you only need to instantiate a Graphics context in the immediate place you need to use it. When you draw into a stored Image cache, you can simply create a local Graphics object on the stack. Keeping multiple Graphics contexts alive that are all drawing into the same image isn’t really advised, and it may be why you’re running into issues.

#5

There’s some internal caching of CoreGraphics-backed images that’s used when you call drawImageAt, and this cache is only cleared when you create a new Graphics that references the internal data. Subsequent drawImageAt calls will fetch the previously rendered image.

Unfortunately there’s no way of clearing this cache directly, so you’ll need to create a new Graphics object before each drawImageAt.