Images parented to DirectFrames fill entire screen

What I’m trying to do:

  1. Create a DirectFrame to frame an image for a GUI element
  2. Have the image actually bounded by the parent frame

Problem code:

    from sys import path
    from os.path import abspath as directory
    from panda3d.core import Filename
    from direct.showbase.ShowBase import ShowBase
    from direct.gui.DirectGui import DirectFrame
    class Base(ShowBase):
        def __init__(self):
            ShowBase.__init__(self)
            #convenience for grabbing resources
            self.dir = Filename.fromOsSpecific(directory(path[0])).getFullpath()
            #brown outer container filling most of the screen
            self.container = DirectFrame(frameColor=(.256,.128,0,1), frameSize=(-.9,.9,-.9,.9))
            #frame for to contain the image
            self.frame = DirectFrame(frameColor=(.9,.9,.9,1), 
                frameSize=(-.1,.1,-.1,.1), 
                pos=(0,0,0), 
                image=self.dir+"/resources/image.png")

    if __name__ == '__main__':
        game = Base()
        game.run()

What I’m getting:
image.png fills the entire screen
if I remove “image=self.dir+”/resources/image.png" from the parameters there is an appropriately-sized off-white frame.

What confounds me:
Creating the frame gives it the appropriate size.
Giving the frame a background image causes the background image to cover the entire screen
The same occurs if I use an OnscreenImage and reparent it to the frame

I’m at my wits end. There’s gotta be something stupid I’m doing, but the documentation is so thin, and says that child elements have properties relative to their parent, so at the very least reparenting an OnscreenImage should scale to the frame.
What’s even weirder is that if I use an Onscreen Image as the parent, instead of a frame, then another onscreen image as the child, I actually get the result I want, but without frame functionality.

What am I doing wrong?

The image feature is used for entirely replacing the generated frame with a card. So the image will not obey the boundaries set by frameSize.

If you use the frameTexture feature instead, it will set the texture onto the generated frame, and it will behave as you expect:

        self.frame = DirectFrame(frameColor=(.9,.9,.9,1),
            frameSize=(-.1,.1,-.1,.1),
            pos=(0,0,0),
            frameTexture=self.dir+"/resources/image.png")

Alternatively, there are several ways to fix this using the image feature:

  1. Use the scale parameter. This scale will apply to children as well.
        self.frame = DirectFrame(frameColor=(.9,.9,.9,1),
            frameSize=(-1,1,-1,1),
            pos=(0,0,0), scale=0.1,
            image=self.dir+"/resources/image.png")
  1. Use the image_scale parameter to only adjust the scale of the image:
        self.frame = DirectFrame(frameColor=(.9,.9,.9,1),
            frameSize=(-.1,.1,-.1,.1),
            pos=(0,0,0), image_scale=0.1,
            image=self.dir+"/resources/image.png")
  1. Parent the frame to the container and scale the container instead:
        self.container = DirectFrame(frameColor=(.256,.128,0,1), frameSize=(-9,9,-9,9), scale=0.1)
        #frame for to contain the image
        self.frame = DirectFrame(
            parent=self.container,
            frameColor=(.9,.9,.9,1),
            frameSize=(-1,1,-1,1),
            pos=(0,0,0),
            image=self.dir+"/resources/image.png")
1 Like

One minor point that I’d like to add: if you don’t set the frameColor to (1, 1, 1, 1), you’ll likely find that your image is dimmed (and possibly coloured, depending on the colour that you set in the “frameColor” keyword-parameter). In short, the “frameColor” acts as a colour-scale; thus a non-white value results in a dimmed and possibly-coloured image, while setting it to white–(1, 1, 1, 1)–results in the original colouring, I believe.

2 Likes

This was extremely helpful, thank you for such a comprehensive answer.
The documentation simply says that the image parameter is " An image to be displayed on the object" and that language led me to believe that it would assign an image to be displayed on the object, instead of replacing the object with a card.

Your third suggestion is the most amenable to my needs, and in fact it’s related to something I tried. I used an OnscreenImage, not a frame, as the child to the frame. This was the first way I had written my code:

    from sys import path
    from os.path import abspath as directory
    from panda3d.core import Filename
    from direct.showbase.ShowBase import ShowBase
    from direct.gui.DirectGui import DirectFrame
    from direct.gui.OnscreenImage import OnscreenImage

    class Base(ShowBase):
       def __init__(self):
        ShowBase.__init__(self)
        #for easier resource grabs
        self.dir = Filename.fromOsSpecific(directory(path[0])).getFullpath()
        self.container = DirectFrame(frameColor=(.256,.128,0,1), 
                                                      frameSize=(-.9,.9,-.9,.9))
        self.frame = DirectFrame(frameColor=(.9,.9,.9,1), 
                                                 frameSize=(-.1,.1,-.1,.1), 
                                                 pos=(0,0,0))
        self.image = OnscreenImage(self.dir+"/resources/image.png", (0,0,0))
        self.image.reparentTo(self.frame)

    if __name__ == '__main__':
      game = Base()
      game.run()

This code produces the same result as the code I posted in my original post.

To avoid this problem, I first got the size of the image, and then set the scale. I have an example for CardMaker, but it can be easily redone. This code creates a 1: 1 scale.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import *

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        iH=PNMImageHeader()
        iH.readHeader(Filename('logo_name.png'))

        tex = loader.loadTexture('logo_name.png')
        cm = CardMaker('logo')
        facecard = NodePath(cm.generate())
        facecard.reparentTo(render2d)
        facecard.setTransparency(1)
        facecard.setTexture(tex, 0)
        facecard.setScale(Vec3(iH.getXSize(), 1, iH.getYSize()) / ((base.win.getYSize()/2)))
        facecard.setPos(-facecard.getTightBounds()[1][0]/2, 0, 0)
        

app = MyApp()
app.run()

You can just query the size of the texture on the texture object directly, especially if you set textures-power-2 none in Config.prc to disable Panda scaling the texture to a power-of-2.