Page 1 of 1

ButtonImageGadget - SetGadgetAttribute doesn't copy image

Posted: Sun Feb 16, 2020 6:41 am
by bytecave
When setting the image of a ButtonImageGadget via SetGadgetAttribute, the underlying image is not copied into the gadget, but instead just the handle to the image is saved in the gadget. This means if you LoadImage an image, you can't call FreeImage on it after setting it in the ButtonImageGadget.

The code below verifies this. In my application (the larger one, not the repro snippet below) I have a thread that repeatedly posts an event to change the image on a ButtonImageGadget, and I think I may have a memory leak because I never free the previous image before loading/setting the next one.

Just wondering if this is by design. "This" meaning that the full image isn't copied into the ButtonImageGadget, just the handle to an image. Perfectly fine and dandy if it's by design, just looking for confirmation.

Code: Select all

Global btn.i
UseJPEGImageDecoder()

Procedure ImageTest()
  Protected img.i

  img = LoadImage(#PB_Any, "<path to any valid jpg>")
  
  If IsImage(img)
    ResizeImage(img, 100, 100, #PB_Image_Raw)
    SetGadgetAttribute(btn, #PB_Button_Image, ImageID(img))
    
    ;FreeImage(img)    ;Uncomment this line to see issue
  EndIf
EndProcedure
  
OpenWindow(0, 0, 0, 120, 120, "ImageTest", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
btn = ButtonImageGadget(#PB_Any, 10, 10, 100, 100, 0)

ImageTest()

;if FreeImage line is uncommented, hovering over buttonimagegadget displays blank button

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Sun Feb 16, 2020 7:26 am
by idle
It makes way more sense that it only sets the pointer to the image rather than copying it.
so yes it's just setting a pointer

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Sun Feb 16, 2020 8:06 am
by netmaestro
PureBasic is designed for speed and efficiency. If there is no need to keep an image in memory in two places, Fred won't do it. You'll come across a lot of the same concept if you use PB much.

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Mon Feb 17, 2020 12:59 am
by bytecave
Thank you both for your feedback. In my scenario, where setting a different image into a buttonimagegadget every several seconds, I now save the last img# (returned from LoadImage) and before I set a new image (on the next call to the function), I FreeImage the previous one. This should prevent the leak I was seeing. --Rich

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Mon Feb 17, 2020 4:09 am
by TI-994A
bytecave wrote:...I may have a memory leak because I never free the previous image before loading/setting the next one...
The memory leak is caused by the dynamic allocation of the image each time it's loaded, essentially creating a new and separate image resource with every load. The issue is simply resolved by manually assigning the image number, which effectively overwrites the previously loaded image of the same number.

So, instead of:

Code: Select all

img = LoadImage(#PB_Any, "<path to any valid jpg>")
assign the image to a constant (or constant value), like so:

Code: Select all

LoadImage(#imgConstant, "<path to any valid jpg>")
This renders the call to FreeImage() redundant, and is therefore not required.

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Mon Feb 17, 2020 7:49 pm
by bytecave
Thanks TI-994A! I am using PB's Form Editor for this application, and it uses LoadImage on 5 different images, in the format Img_wndMain_1 = LoadImage(...). Since I can't assume the internal implementation (can't know which values it chooses for image numbers) of LoadImage, I'd have to chose image_constants at runtime, by generating a (random probably) number and testing the values against each of the Img_wndMain_* values to make sure there was no collision.

Now, if there is a way to ensure that my constant values don't interfere with the range that PB uses for its LoadImages calls, perhaps a LoadImage corollary to #PB_Event_FirstCustomValue, then I could define my constants in that range.

Thanks again! --Rich

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Tue Feb 18, 2020 5:42 am
by TI-994A
bytecave wrote:I am using PB's Form Editor for this application, and it uses LoadImage on 5 different images, in the format Img_wndMain_1 = LoadImage(...)...
That's quite alright, as the images would still be loaded only once. The dynamically-assigned image numbers can then be used to set the relevant image gadgets without the need to reload them each time. Alternatively, there is also the option of using static image numbers by deselecting the #PB_Any option for the images in the Form > Image Manager menu.
bytecave wrote:...Since I can't assume the internal implementation (can't know which values it chooses for image numbers) of LoadImage, I'd have to chose image_constants at runtime, by generating a (random probably) number and testing the values against each of the Img_wndMain_* values to make sure there was no collision...
There's no risk of that as the #PB_Any directive assigns object numbers quite beyond the recommended limits of manually assigned ones.

Code: Select all

imageFile$ = #PB_Compiler_Home + "Examples\Sources\Data\Purebasic.bmp"
dynamicNumber = LoadImage(#PB_Any, imageFile$)
Debug dynamicNumber   ; very high number
Now, try this (with the debugger on):

Code: Select all

imageFile$ = #PB_Compiler_Home + "Examples\Sources\Data\Purebasic.bmp"
LoadImage(100000, imageFile$)
The debugger will throw this error:

Code: Select all

[ERROR] #Image object number is very high (over 100000), are You sure of that ?
So, as long as the recommendations are adhered to, static and dynamic object numbers can be safely used alongside each other without risk of overlapping.

Another noteworthy point: object numbers are referenced by their object types, meaning that the same object number can be used simultaneously for different object types. A window, a gadget, an image, a font, and so forth, can have identical object numbers without them overwriting one another.

Code: Select all

commonObjectNumber = 0

LoadFont(commonObjectNumber, "Arial", 18, #PB_Font_Italic)
LoadImage(commonObjectNumber, #PB_Compiler_Home + "Examples\Sources\Data\Purebasic.bmp")

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(commonObjectNumber, 0, 0, 400, 100, "Object Numbers", wFlags)

ImageGadget(commonObjectNumber, 0, 0, 0, 0, ImageID(commonObjectNumber))

; must use a different object number or it will overwrite the image gadget
TextGadget(1, 0, 50, 380, 50, "Arial Italic Font Size 18", #PB_Text_Right)
SetGadgetFont(1, FontID(commonObjectNumber))

While WaitWindowEvent() ! #PB_Event_CloseWindow : Wend

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Sat Feb 22, 2020 6:16 am
by bytecave
Quick wrap-up thank you. :)

This convinced me:
Alternatively, there is also the option of using static image numbers by deselecting the #PB_Any option for the images in the Form > Image Manager menu.
It's obvious now that you've pointed it out :wink: and totally under my control; I can ensure no conflict between Form Editor objects and my own objects.

You did a lot of work clarifying this next bit, and I really appreciate the time you took and the sharing of your knowledge. I am, however, a bit gun shy about depending on #PB_Any always returning numbers in a range that doesn't conflict with my own static assignments. Unless that's in the documentation, it's a PB internal operation that is (technically) subject to change at any time. Not saying that it's not in the documentation (I couldn't find it though), and definitely not saying that you aren't correct about how it works now, just that I prefer the conservative route of not depending upon undocumented behaviors.

Anyhow, my app has been running for days with no memory leak and my lovely wall tablet mounted picture slideshows it enables are all merrily slideshowing all over the house!

YOU, my friend, are awesome. :)

Re: ButtonImageGadget - SetGadgetAttribute doesn't copy imag

Posted: Sat Feb 22, 2020 6:47 am
by TI-994A
bytecave wrote:...gun shy about depending on #PB_Any always returning numbers in a range that doesn't conflict with my own static assignments...
You're right; it's not impossible. Always good to stick to our preferred programming styles.

And thank you for your extremely kind words. They're truly appreciated and I'm glad to have been of any help. :D