Ren'Py Composite/LiveComposite vs Layered Images

PearlNecklaceProductions

Newbie
Game Developer
Sep 23, 2019
56
64
Hi everyone.

So for the first release we did not bother learning how to just draw the character once and than dress it up and put the expressions on it as it needed them, but just exported the expression we needed. This is not just a lengthy procedure but also so many different images with all variables take quite some space. I have recently started learning on our new character to compose her in code. If she will have 3 different eyebrow positions, 5 eyes, 2 hairstyles, hand positions, outfits and so on, it will take quite some time to compose everything completely, so I want to do it right.




From my rough understanding of reading the documentation on Layered Images, everything that you can achieve with Composite you can also do there for this purpose.
Am I correct? I think I noticed a noticeable delay when I show the Composite consisting of multiple images and since I did not do much of a work, it should be easy enough to switch everything to Layered.

What are you guys experiences on this? In which case is performance better and which case offers more flexibility (adding new layers, modifying them - im able to do im.Scale, Im.Crop with composites). I think Layered were added later on, so they should be better, but I'd rather ask here and see what I should continue with, so I can get as much performance and small game as possible.
 
  • Like
Reactions: masterdragonson

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,101
14,746
From my rough understanding of reading the documentation on Layered Images, everything that you can achieve with Composite you can also do there for this purpose.
Strictly speaking, yes, but layered images goes way further than the Composite displayable. It's more a way to define complex images that initially needed both Composite and displayable to be describe. What can now be wrote like this :
Code:
layeredimage eileen:
     always:
          "character/eileen/body.png"
     group outfit:
          attribute classy:
              "character/eileen/dress_classy.png"
          attribute slutty:
              "character/eileen/dress_slutty.png"
Initially had to be wrote like that :
Code:
image eileenOutfit = ConditionSwitch(
        "eileenDress == 'classy'", "character/eileen/dress_classy.png",
        "eileenDress == 'slutty'",  "character/eileen/dress_slutty.png" )

image eileen = Composite(
        (100,100),
        (0,0), "character/eileen/body.png",
        (0,0), "eilenOutfit" )
Note that the doc example don't show it explicitly, but the if used inside the definition of a layered image follow the effective structure of an if block, and isn't limited to Boolean values. This permit to have layered images following the example for ConditionSwitch :
Code:
layeredimages eileen:
     always:
          "character/eileen/body.png"
     group outfit:
          attribute classy:
              "character/eileen/dress_classy.png"
          attribute slutty:
              "character/eileen/dress_slutty.png"

    if eileenBeer > 4:
          "character/eileen/face_drunk.png"
    elif eileenBeer > 2:
           "character/eileen/face_tipsy.png"
    else:
          "character/eileen/face.png"
And finally, layered images are created dynamically, which isn't the case of Composite one. This imply that you can also have text interpolation inside the definition of a layered image, which can be useful.
Firstly because the layered images syntax is relatively limited. By example, you can have if structures, but you can't put a group nor an attribute in them, nor have the if inside a group. Therefore, if you want the character to have a regular body and a tattooed one, while possibly be tanned or not, you would need this :
Code:
layeredimages eileen:
     group body:
          attribute tattooTan:
              "character/eileen/body_tattoo_tan.png"
         attribute tattoo:
              "character/eileen/body.png"
         attribute Tan:
              "character/eileen/body.png"

     group outfit:
          attribute classy:
              "character/eileen/dress_classy.png"
          attribute slutty:
              "character/eileen/dress_slutty.png"
Which start to add a lot since now you'll have to write this show eileen tattooTan classsy to display the image. With all the other group you can possible have added to it.
Secondly because in case like this one, the variation is intended to be present in more than one image, and to depend of the character choice. Therefore, you have to deal with the fact that the character have or not a tattoo :
Code:
    if eileenTattoo is True:
        show eileen tattooTan classy
else:
        show eileen tan classy
But with interpolation, you can reduce the size of each image name, while getting ride of the need of the test :
Code:
layeredimages eileen:
     always:
         "character/eileen/body[isTattooed][hasTan].png"

     group outfit:
          attribute classy:
              "character/eileen/dress_classy.png"
          attribute slutty:
              "character/eileen/dress_slutty.png"

default isTattooed = ""
default hasTan = ""
The image for the body will depend of the value of isTattooed and hasTan. By default their value are empty, and the body will be "character/eileen/body.png". Then later you can have this :
Python:
    menu:
         "Ask her to have a tattoo ?"
         "no":
                pass
                # The image will stay
                # charactere/eileen/body.png"
         "no, but some tan would be good":
                $ hasTan = "_tan"
                # Now the image displayed will be 
                # character/eileen/body_tan.png"
         "yes":
                $ isTattooed = "_tattoo"
                # Now the image displayed will be 
                # character/eileen/body_tatoo.png"
         "yes, and she need some tan on top of this":
                $ isTattooed = "_tattoo"
                $ hasTan = "_tan"
                # Now the image displayed will be 
                # character/eileen/body_tatoo_tan.png"
And you'll not have to test this later in the game, when you'll display the image, the computation will be automatically done, and the right body will be displayed, whatever the effective value of the two variables.

You can use the same thing to have variation regarding the tan :
Python:
    menu:
         "Do you want her to have tan lines ?"
         "Yes":
                $ hasTan = "_tanlines"
                # Now the image displayed will be 
                # charactere/eileen/body_tanlines.png"
         "Yes, but she'll tan topless":
                $ hasTan = "_tanhalf"
                # Now the image displayed will be 
                # character/eileen/body_tanhalf.png"
         "No, she'll tan on the nude":
                $ hasTan = "_tannolines"
                # Now the image displayed will be 
                # character/eileen/body_tannolines.png"
         "I don't even want her to tan !":
                pass
                # The image will stay 
                # character/eileen/body.png"

Am I correct? I think I noticed a noticeable delay when I show the Composite consisting of multiple images and since I did not do much of a work, it should be easy enough to switch everything to Layered.
If there's a delay with Composite, there will probably be one with layered images :
  • Composite displayables are pre-proceeded when the displayable is created, then when displayed, the images are loaded and displayed one on top of the others.
  • Layered images are proceeded each time the image is displayed, then the images are loaded and displayed on top of the others, and the whole thing is refreshed at least after each interaction.
Therefore, layered images are slower than Composite displayable.


[...] so I can get as much performance and small game as possible.
This let me puzzled. Ren'py can be slow on old computer and tablet/phone with small amount of RAM, but even in those case it should be really sensible with images if they are correctly defined and not oversized. It's only with movies, and animated images, that it can be effectively percieved.
From my point of view, the performance issue can't be solved on Ren'py side, but on yours.
 

PearlNecklaceProductions

Newbie
Game Developer
Sep 23, 2019
56
64
This let me puzzled. Ren'py can be slow on old computer and tablet/phone with small amount of RAM, but even in those case it should be really sensible with images if they are correctly defined and not oversized. It's only with movies, and animated images, that it can be effectively percieved.
From my point of view, the performance issue can't be solved on Ren'py side, but on yours.
Thank you so much for a detailed description with examples. It is exactly what I needed to know.
As for the performance, turns out the images in composite were all 8000 x 4500 px (I resize them in renpy to be sure, and I did not notice) so the load is now not noticable.
 

ishigreensa

New Member
Jul 6, 2019
9
1
Had I stumbled on this before, I could have corrected your LiveComposite image information a bit...
Probably too late now?

Anyway, you can make complex composite images if you declare the image in an init python.

init python:
$ girl = "Amy"
$ shirt = 1
$ pants = 1
$ legs = "apart"
$ arms = "folded"
$ mouth = "smile"
$ eyes = "open"
$ dirty = 0

You don't type the $ in the init python block, but I showed them as strings so you know the variables....

def draw_NPC(st,at):
return LiveComposite(
(361,650),
(0,0),("images/%s"%girl + "_%s"%arms + "_%s"%legs + "base.png"), ## this gives you the base nude image
(0,0),("images/%s"%girl + "_%s"%eyes + "_%s"%mouth + "head.png" ), ## This gives you the head
(0,0),("images/%s"%girl + "_%s"%arms + "_%d.png"%shirt), ## This gives you the shirt.
(0,0),("images/%s"%girl + "_%s"%legs + "_%d"%pants + "_%d.png"%dirty) ##the pants
),.1

Then init....
init:
image G = DynamicDisplayable(draw_NPC)

$ Girl = Character("[girl]")

Now, by manipulating the variables, your image immediately changes without having to type out show again unless you wanted to hide your image.
Of course, you do have to type out show G the first time you want it used, but after that, as long as the image is not
hidden, you never have to type show again to change your image.
You can even automatically change the girl shown by simply changing the variable girl.
$ girl = Amy
$ girl = Connie
also, both your image and your Girl speakers will change character with this... so you don't have to remember what name you are on when talking for an NPC. Just type
Girl "blah blah blah"
And the girl's tag will automatically change to the same tag as the image of the girl you are currently using.

I don't know if LayeredIMages can do that, but now, I'm currious to see....
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,101
14,746
Anyway, you can make complex composite images if you declare the image in an init python.
[...]
Wow, this is an overly complicated way to deal with the problem.

Not only Layered images are way easier to use in this case, but you can still do it way more simply while still using LiveComposite.

Firstly you don't need to use a DynamicDisplayable. Both the show and scene statements accept an expression in place of a displayable. Therefore you could have called the function directly. Which imply that you also don't need the parameters for draw_NPC.
Code:
[...]
    def draw_NPC():
        return LiveComposite( [...] )
[...]
label whatever:
    show expression draw_NPC()
Secondly, there's already a way to deal with the fact that a LiveComposite displayable compose the image at creation time. In place of providing a path to the image, you provide the name of a displayable. Then you define the said displayable in such way that it will use interpolation (what was in a time long ago, a DynamicImage).
Python:
# On a side note, this is how you initialize a savable variable.
default girl = "Amy"
default shirt = 1
[...]

image baseNude = "images/[girl]_[arms]_[legs]base.png"
image head = "images/[girl]_[eyes]_[mouth]head.png"
image shirt = "images/[girl]_[arms]_[shirt].png"
image pants = "images/[girl]_[legs]_[pants]_[dirty].png"

image G = LiveComposite(
    ( 361, 650),
    ( 0, 0 ), "baseNude",
    ( 0, 0 ), "head",
    ( 0, 0 ), "shirt",
    ( 0, 0 ), "pants" )

I don't know if LayeredIMages can do that, but now, I'm currious to see....
Well, would you had took the time to read the whole thread, that you would have had the answer.
 

PearlNecklaceProductions

Newbie
Game Developer
Sep 23, 2019
56
64
I decided to use LayeredImages and have switched all scenes and characters to be using them. I find them easy enough. Not only that, but with renpy's own tool, interactive director, even non developers can modify the character expressions while playing the game and just clicking on the menu which expression you want.
 

AmazonessKing

Amazoness Entrepreneur
Aug 13, 2019
1,898
2,902
Is interactive director good at all? I found it either lacking or just useless when autoreload is a thing.
 

PearlNecklaceProductions

Newbie
Game Developer
Sep 23, 2019
56
64
You just have to make sure you do not do any more edits to the code in IDE while you are working with director - otherwise it will crash. It is good for checking character expressions if you have images in layeredimage names eye1, eye2, eye3... and combine with mouth1, mouth2.... As sometimes you can only see during the gameplay which expression you want. It helps just a bit, but you know, minute here, minute there....
 

ishigreensa

New Member
Jul 6, 2019
9
1
Wow, this is an overly complicated way to deal with the problem.

Not only Layered images are way easier to use in this case, but you can still do it way more simply while still using LiveComposite.

Firstly you don't need to use a DynamicDisplayable. Both the show and scene statements accept an expression in place of a displayable. Therefore you could have called the function directly. Which imply that you also don't need the parameters for draw_NPC.
Code:
[...]
    def draw_NPC():
        return LiveComposite( [...] )
[...]
label whatever:
    show expression draw_NPC()
Secondly, there's already a way to deal with the fact that a LiveComposite displayable compose the image at creation time. In place of providing a path to the image, you provide the name of a displayable. Then you define the said displayable in such way that it will use interpolation (what was in a time long ago, a DynamicImage).
Python:
# On a side note, this is how you initialize a savable variable.
default girl = "Amy"
default shirt = 1
[...]

image baseNude = "images/[girl]_[arms]_[legs]base.png"
image head = "images/[girl]_[eyes]_[mouth]head.png"
image shirt = "images/[girl]_[arms]_[shirt].png"
image pants = "images/[girl]_[legs]_[pants]_[dirty].png"

image G = LiveComposite(
    ( 361, 650),
    ( 0, 0 ), "baseNude",
    ( 0, 0 ), "head",
    ( 0, 0 ), "shirt",
    ( 0, 0 ), "pants" )



Well, would you had took the time to read the whole thread, that you would have had the answer.
I do thank you for this advice, though. Now I see a simpler way to do it, I think I will be practicing with trying to do it this way. I like simple when possible.

Also, question?
I read somewhere that show expression [pictured displayable] was difficult when you wanted to change to a completely different displayable and needed to hide the first one. Is that not still true? Especially if you want to show it again a bit later on after the other displayable is on the screen?

Showing direct images is easy to hide by just using show and hide or scene each time the scene changes... but I got the feeling reading the documentation that it would take more than that to hide the show expression abc.
 
Last edited:

ishigreensa

New Member
Jul 6, 2019
9
1
Wow, this is an overly complicated way to deal with the problem.

Not only Layered images are way easier to use in this case, but you can still do it way more simply while still using LiveComposite.

Firstly you don't need to use a DynamicDisplayable. Both the show and scene statements accept an expression in place of a displayable. Therefore you could have called the function directly. Which imply that you also don't need the parameters for draw_NPC.
Code:
[...]
    def draw_NPC():
        return LiveComposite( [...] )
[...]
label whatever:
    show expression draw_NPC()
Secondly, there's already a way to deal with the fact that a LiveComposite displayable compose the image at creation time. In place of providing a path to the image, you provide the name of a displayable. Then you define the said displayable in such way that it will use interpolation (what was in a time long ago, a DynamicImage).
Python:
# On a side note, this is how you initialize a savable variable.
default girl = "Amy"
default shirt = 1
[...]

image baseNude = "images/[girl]_[arms]_[legs]base.png"
image head = "images/[girl]_[eyes]_[mouth]head.png"
image shirt = "images/[girl]_[arms]_[shirt].png"
image pants = "images/[girl]_[legs]_[pants]_[dirty].png"

image G = LiveComposite(
    ( 361, 650),
    ( 0, 0 ), "baseNude",
    ( 0, 0 ), "head",
    ( 0, 0 ), "shirt",
    ( 0, 0 ), "pants" )



Well, would you had took the time to read the whole thread, that you would have had the answer.
I had read the whole reply twice, when I answered.
No, you didn't look like you had taken into account the idea of moving the shirt or pants with the figure in your layered explanation, at least not explicitly, so I was confused. I reread everything again, and I realized I may have made a mistake. Sometimes, just because you read something through, even twice, well, not YOU, but at least for me, it doesn't always mean I picked up everything in there.

I saw you put variables in the [always] layer of the image, but you didn't explicitly show pudding variables in the other layers, and something in the documentations had at first made me think you couldn't. Probably because even with trial and error, I found that if statements didn't work to decide on exposing a layer or not.

In short, I didn't see a way to put variables in any part of the image except the always from your example, and the documentation and the fact that if...:
group ...:
didn't work.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,101
14,746
I read somewhere that show expression [pictured displayable] was difficult when you wanted to change to a completely different displayable and needed to hide the first one.
The statement isn't difficult, it act according to its behavior. It's designed to show a sprite (generally a character) over the actual scene (generally a background). Therefore, yes, when you don't want to show this sprite anymore, you need to use the statement in order to remove it from the screen.
This said, there's an exception to this rule ; or more precisely a simplification to show use. When you just change the tags of an image that you've shown, the statement will update what is displayed in place of displaying an additional image.
Python:
image girl happy = "girl/happy.png"
image girl sad = "girl/sad.png"

label whatever:
    # Display images/girl/happy.png
    show girl happy
    [...]
    if badMove:
        # Replace images/girl/happy.png
        # by images/girl/sad.png
        show girl sad
    [...]
    # Finally, remove whatever images/girl/happy.png
    # or images/girl/sad.png depending of which one is displayed
    hide girl

but I got the feeling reading the documentation that it would take more than that to hide the show expression abc.
It don't take more than that, hide expression draw_NPC() works perfectly. You can even give a parameter to the function without problem :
Python:
init python:
    def draw_NPC( girl ):
        if girl == "amber":
           return WHATEVER
        elif girl == "zara":
           return WHATEVER
        [...]

label whatever:
    show expression draw_NPC( "amber" )
    [...]
    show expression draw_NPC( "zara" )
    [...]
    # Will remove amber, but keep zara.
    hide expression  draw_NPC( "amber" )

I saw you put variables in the [always] layer of the image, but you didn't explicitly show pudding variables in the other layers,
Because they don't needed it. But yes you can have things like :
Code:
layeredimages eileen:
     always:
         "character/eileen/body[isTattooed][hasTan].png"

     group outfit:
          attribute classy:
              "character/eileen/dress_classy[isTorn].png"
          attribute slutty:
              "character/eileen/dress_slutty[isTorn].png"

default isTattooed = ""
default hasTan = ""
default isTorn = ""

label whatever:
    menu:
        "be gentle":
            [...]
        "be rude":
            $ isTorn = "_torn"
            [...]

and something in the documentations had at first made me think you couldn't. Probably because even with trial and error, I found that if statements didn't work to decide on exposing a layer or not.
As I wrote :
[...]you can have if structures, but you can't put a group nor an attribute in them, nor have the if inside a group.
Therefore, the said if isn't intended to command the visibility of a layer, but is a layer by itself ; like explicitly said by : "Finally, the if statement adds a layer that selects between displayables using a Python statement."

Code:
layeredimages eileen:
     always:
          "character/eileen/body.png"

    [...]

    if eileenBeer > 4:
          "character/eileen/face_drunk.png"
    elif eileenBeer > 2:
           "character/eileen/face_tipsy.png"
    else:
          "character/eileen/face.png"
is the equivalent of :
Code:
layeredimages eileen:
     always:
          "character/eileen/body.png"

    [...]

    group face:
          attribute drunk:
              "character/eileen/face_drunk.png"
          attribute tipsy:
              "character/eileen/face_tipsy.png"
          attribute sober default:
              "character/eileen/face.png"
the difference being that with this second syntax, you need to add the "drunk" or "tipsy" tag by yourself. This while the first syntax decide by itself which face should be used.
 
  • Like
Reactions: ishigreensa

ishigreensa

New Member
Jul 6, 2019
9
1
...
[/code]
the difference being that with this second syntax, you need to add the "drunk" or "tipsy" tag by yourself. This while the first syntax decide by itself which face should be used.
Thank you very much for this explanation.
I think I had successfully used if in a dynamicdisplayable before... so I tried to do it with layeredimage the same way.
Of course, based on your explanation, I now see why it didn't work.

Thank you very much for explaining this stuff to me.
I programmed a bit when I was in high school, but thsoe apple II E machines were so easy to program in basic compared with the stuff we do with this. Of course, they couldn't handle this kind of code, I don't think just because basic didn't have these kinds of parameters.

Other than experimenting with Renpy since about 2011 or so, I really haven't had any experience with modern programming or anything anymore advanced than basic.

So again, thank you so much for explaining this stuff. I thought I was being helpful because that code worked for me before, but and it seemed the questioner had no idea of how to do anything in putting variables into the image at all... from how I read it. Not that I might not have misread it.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,101
14,746
So again, thank you so much for explaining this stuff.
You're welcome.


I thought I was being helpful because that code worked for me before,
You were, in your way. My own correction was more an information than anything else, and clearly not intended as a criticism. Despite starting by a one week holiday, this September is relatively busy and hard, so sorry if perhaps I sounded harsh, it was the tiredness, not me.
 

ishigreensa

New Member
Jul 6, 2019
9
1
Okay, so here is one...
If I do this, do you think it will work?
I don't have images yet, so I can't test it, yet on my own.

layeredimage Amy:
always:
"images/bg/[room][roomsub][roomdir].png"
"images/girls/Amy/[body][arms][legs].png"
group face:
attribute eyesopen:
"images/girls/Amy/face[aemotion][amouth].png"

default room = "school_classroom"
default roomsub = "" # use _numbers if more than one view of this room and direction.
default roomdir = "_north"

default body = "standing"
default arms = "_relaxed"
default legs = "_apart"

default aemotion = "_happy"
default amouth = "" #mouth of image is closed. _talking if I want it open as though character is talking.

declared images in folders:
images/bg/school_classroom_north.png
images/girls/Amy/face_happy.png
images/girls/Amy/standing_relaxed_apart.png
images/girls/Amy/standing_redshirt_relaxed.png
images/girls/Amy/standing_bluejeans_apart_dirty.png

The last two will have a shirt group and a pants group.