Layered Images — Ren'Py Documentation

# Layered Images

When a sprite-set gets to a certain level of complexity, defining every possible combination may become unwieldy. For example, a character with 4 outfits, 4 hairstyles, and 6 emotions already has 96 possible combinations. Creating static images for each possible combination would consume a lot of disk space and programmer time.

To address this use case, Ren'Py introduces a way of defining an image consisting of multiple sprites, organized in layers. (For the purpose of this, consider layers to be the layers in a paint program like Photoshop or the GIMP, and not the layers used elsewhere in Ren'Py.) Elements of these layers can be selected by attributes provided to the image, or by conditions that are evaluated at runtime.

These images can be declared using the `layeredimage` statement, using a specific language. The `LayeredImage()` object is its Python alternative, it's not a displayable but can be assigned to an image statement and used like one.

The bottom of this page contains advice and examples of use.

## Defining Layered Images

The language used to define layered images consists of only a few statements, to introduce the layers. Here is an example which, while not making much practical sense, is technically correct and outlines the layeredimage syntax:

    layeredimage augustina:
        zoom 1.4
        at recolor_transform

        always:
            "augustina_base"

        attribute base2 default

        group outfit:
            attribute dress default:
                "augustina_dress"
            attribute uniform

        group face auto:
            pos (100, 100)
            attribute neutral default

    label start:
        show augustina # displaying dress and neutral
        aug "I like this dress."

        show augustina happy # auto-defined in the auto group
        aug "But what I like even more..."

        show augustina uniform -happy # uniform replaces dress, neutral replaces happy
        aug "Is this uniform !"

### Layeredimage

The `layeredimage` statements opens the show. The statement is part of the Ren'Py script language, and runs at init time. Like the Image Statement with ATL Block, it takes an image name and opens a block, although what's in the block differs greatly. The image name may contain spaces, just like any other image name in Ren'Py.

Inside the block will fit the statements described further down, as well as the following optional properties.

image_format

  When a given image is a string, and this is supplied, the image name is interpolated into image_format to make an image file. For example, "sprites/eileen/{image}.png" will look for the image in a subdirectory of sprites. (This is not used by auto groups, which look for defined images and not for image files.)

format_function

  A function that is used instead of `layeredimage.format_function()` to format the image information into a displayable, during the image definition at init time.

attribute_function

  A function or callable that is used to tweak what attributes end up being displayed. It is called with a set of attributes supplied to the image, and should return the set of attributes that should be used to select layers. It can be used to express complex dependencies between attributes, or to select attributes at random. See Selecting attributes to display for more information about when and how this is called.

at

  A transform or list of transforms that are applied to the layered image.

transform properties

  If given, these are used to construct a transform that is applied to the displayable.

offer_screen

  If this is True, the layeredimage will place its children, and size its children with variable size, like it was given an area matching the whole screen of the game. If it is False, the said behaviors will be done while taking into account the available area, which for example will be smaller in an hbox containing other elements, and the display of the layeredimage will not be consistent every time it is shown.

  If None, the default, falls back to `config.layeredimage_offer_screen`, which defaults to True.

### Always

The `always` statement declares an image that is always shown inside the layeredimage, and which will not be attached to an attribute. It must be supplied a displayable, and can also take properties. Both can be placed on the same line or inside a block.

The `always` statement takes the following properties:

if_all

  A string or list of strings giving the names of attributes. If this is given, this layer is only displayed if all of the named attributes are present.

if_any

  A string or list of strings giving the names of attributes. If this is given, this layer is only displayed if any of the named attributes are present.

if_not

  A string or list of strings giving the names of attributes. If this is given, this layer is only displayed if none of the named attributes are present.

transform properties

  If given, these are used to construct a transform that is applied to the displayable.

at

  A transform or list of transforms that are applied to the provided displayable.

#### If

The `if` statement (or more fully the if-elif-else statement) allows you to supply one or more conditions that are evaluated at runtime. Each condition is associated with a displayable, with the first true condition being the one that is shown. If no condition is true, the `else` layer is shown if given.

A more complete example of an `if` statement might look like:

    if glasses == "evil":
        "augustina_glasses_evil"
    elif glasses == "normal":
        "augustina_glasses"
    elif glasses == "funky":
        "augustina_glasses_clown"
    else:
        "augustina_nose_mark"

Each clause must be given a displayable. It can also be given these properties:

if_all

  A string or list of strings giving the names of attributes. If this is given, this condition is only considered if all of the named attributes are present.

if_any

  A string or list of strings giving the names of attributes. If this is given, this condition is only considered if any of the named attributes are present.

if_not

  A string or list of strings giving the names of attributes. If this is given, this condition is only considered if none of the named attributes are present.

transform properties

  If present, these are used to construct a transform that is applied to the displayable.

at

  A transform or list of transforms that are applied to the displayable.

The `if` statement is transformed to a `ConditionSwitch()` when the `layeredimage` statement runs.

layeredimage.predict_all = None

  Sets the value of predict_all for the ConditionSwitches produced by layeredimages' `if` statements.

When `predict_all` is not true, changing the condition of the if statement should be avoided while the layered image is shown or about to be shown, as it would lead to an unpredicted image load. It's intended for use for character customization options that don't change often.

### Attribute

The `attribute` statement adds a displayable that is part of the resulting image when the given attribute is used to display it. For example, using the previous example, calling `show augustina dress` will cause the "augustina_dress" to be shown as part of the "augustina" image.

An `attribute` clause takes an attribute name, which is one word. It can also take two keywords. The `default` keyword indicates that the attribute should be present by default unless an attribute in the same group is called. The `null` keyword prevents this clause from getting attached a displayable, which can be useful for bookkeeping and to build conditional display conditions using if_all, if_any, if_not, attribute_function, `config.adjust_attributes` or `config.default_attributes`.

The same attribute name can be used in multiple `attribute` clauses (and in auto-defined attributes as part of `auto` groups, more about that later), with all the corresponding displayables being shown at the same time (the if_all, if_any, and if_not properties can tweak this).

If the displayable is not explicitly given, it will be computed from the name of the layeredimage, the group (if any), the group's variant (if any), and the attribute. See the pattern section for more details.

The attribute statement takes the following properties:

if_all

  A string or list of strings giving the names of attributes. If this is present, this layer is only displayed if all of the named attributes are present.

if_any

  A string or list of strings giving the names of attributes. If this is present, this layer is only displayed if any of the named attributes are present.

if_not

  A string or list of strings giving the names of attributes. If this is present, this layer is only displayed if none of the named attributes are present.

transform properties

  If present, these are used to construct a transform that is applied to the layer.

at

  A transform or list of transforms that are applied to the layer.

The if_* clauses' test is based upon the list of attributes of the resulting image, as explained here, but it does not change that list.

    layeredimage eileen:
        attribute a
        attribute b default if_not "a"
        attribute c default if_not "b"

In this example, the `b` and `c` attributes are always part of the attributes list (because of their `default` clause). When calling `show eileen a`, the `a` attribute will be displayed as requested, and the `b` attribute will not, due to its `if_not` property. But even if not displayed, the `b` attribute will still be part of the attributes list, which means the `c` attribute will still not display.

### Group

The `group` statement groups attributes together, making them mutually exclusive. Unless the group is `multiple`, when attributes a and b are in the same group, it is an error to include both of the attributes at the same time, with `show eileen a b` for example. In the same example, calling attribute a will hide attribute b, and vice versa. However, note that it's fine for several `attribute` clauses to be passed the same name, even within the same group. In that case, they will be considered as one attribute containing several sprites - more about that at the end of this section.

The `group` statement takes a name. The name isn't used for very much, except to generate the default names of attributes inside the group. That is not the case for `multiple` groups in which the name doesn't have any use or impact.

The name may be followed by the `auto` keyword. If it's present, after any attributes in the group have been declared, Ren'Py will scan its list of images for those that match the group's pattern (see below), with the specificity that in that case, a multiple group's name is part if the pattern, and that the `format_function` passed to the layeredimage is ignored. Any images that are found, except those corresponding to explicitly declared attributes, are then added to the group as if declared using the `attribute` statement inside the group's block. See the Examples section for a practical demo.

This can be followed by the `multiple` keyword. If present, no incompatibility is applied to the attributes declared inside the block. This is useful to have a group auto-define multiple attributes that are not exclusive, or to apply the same properties to a set of attributes at once. This conflicts with the `default` keyword being given to one of the attributes. Note that `multiple` groups are very different from other, normal groups, and that most of what's true about groups doesn't apply to them.

After these optional keywords, properties can then be declared on the first line of the group, and it can take a block containing properties and attributes.

The group statement takes the properties `attribute` does - such as `if_any`, `at` and so on. Properties supplied to the group are passed to the attributes inside the group, unless overridden by the same property of the attribute itself. In addition, there are two properties which are specific to groups:

variant

  If given, this should be a string. If present, it adds an element that becomes part of automatically-generated image names, and of the pattern used to search for images when automatically defining attributes in `auto` groups.

prefix

  If given, this is a prefix that is concatenated using an underscore with the manually or automatically defined attribute names. So if prefix is "leftarm", and the attribute name "hip" is encountered, `show eileen leftarm_hip` will display it.

An attribute may also be part of several groups, in which case the attribute is incompatible with every other attribute in every group it's part of. This can be useful for example for a dress attribute, to make it hide both any top and any pants that may be showing when it gets displayed:

    layeredimage eileen:
        attribute base default
        group bottom:
            attribute jeans default
            attribute dress null
        group top:
            attribute shirt default
            attribute dress

When several `group` blocks with the same name are defined in the same layeredimage, they are considered to be different parts of a single group. For example:

    layeredimage eileen sitting:
        attribute base default
        group arms variant "behind":
            attribute on_hips
            attribute on_knees
            attribute mixed
        attribute table default
        group arms variant "infront":
            attribute on_table default
            attribute holding_margarita
            attribute mixed

In this example, `eileen_sitting_arms_behind_mixed.png` will contain her left arm behind the table, and `eileen_sitting_arms_infront_mixed.png` will contain her right arm on the table. When calling `show eileen sitting mixed`, the two images will be shown at the same time, respectively behind and in front of the table. In this example, the on_hips attribute is incompatible with the on_table attribute, because even though they are not declared in the same block, they are both in the same group.

## Pattern and format function

The pattern, used to find images for attributes when they are not explicitly given one, consists of:

- The name of the layeredimage, with spaces replaced with underscores.

- The name of the group, if any and if the group is not `multiple`.

- The name of the variant, if there is one.

- The name of the attribute.

all combined with underscores. For example, if we have a layered image with the name "augustina work", and the group "eyes", this will match images that match the pattern augustina_work_eyes_attribute. With a variant of blue, it would match the pattern augustina_work_eyes_blue_attribute. In the following example:

    layeredimage augustina work:
        group eyes variant "blue":
            attribute closed

The attribute is linked to the image "augustina_work_eyes_blue_closed". That can resolve to an image file named `augustina_work_eyes_blue_closed.png`, but it can also be defined explicitly using the Image Statement for example.

If you want a `multiple` group's name to be included in the pattern, you can use the following syntax:

    group addons multiple variant "addons"

All of the pattern behavior can be changed using a format_function: `layeredimage.format_function()` is the function used under the hood to implement the behavior described above. You can see what arguments it takes, in case you want to supply your own format_function to replace it.

layeredimage.format_function(what, name, group, variant, attribute, image, image_format, **kwargs)

  This is called to format the information about an attribute or condition into a displayable. This can be replaced by a creator, but the new function should ignore unknown kwargs.

  what

    A string giving a description of the thing being formatted, which is used to create better error messages.

  name

    The name of the layeredimage.

  group

    The group of an attribute, None if not supplied or if it's part of a condition.

  variant

    The variant argument to the group, or None if it is not supplied.

  attribute

    The attribute itself.

  image

    Either a displayable or string.

  image_format

    The image_format argument of the LayeredImage.

  If image is None, then name, group (if not None), variant (if not None), and attribute are combined with underscores to create image, which will then be a string.

  If images is a string, and image_format is not None, image is formatted into the string to get the final displayable.

  So if name is "eileen", group is "expression", and attribute is "happy", image would be set to "eileen_expression_happy". If image_format is "images/{image}.png", the final image Ren'Py finds is "images/eileen_expression_happy.png". But note that it would have found the same image without the format argument.

## Proxying Layered Images

Sometimes, it can be useful (and even necessary) to proxy a layered image, to use the same layered image in multiple places. One reason for this would be to have a transformed version of a given layeredimage, while another would be to use it as a side image.

The `LayeredImageProxy()` object does this, taking one layered image and duplicating it somewhere else. For example:

    image dupe = LayeredImageProxy("augustina")

creates a duplicate of the image that can be displayed independently. This also takes a transform argument that makes it useful to position a side image, like this:

    image side augustina = LayeredImageProxy("augustina", Transform(crop=(0, 0, 362, 362), xoffset=-80))

See the difference:

    image sepia_augustina_one = Transform("augustina", matrixcolor=SepiaMatrix())
    image sepia_augustina_two = LayeredImageProxy("augustina", Transform(matrixcolor=SepiaMatrix()))

`sepia_augustina_one` will be a sepia version of the original version of the "augustina" layeredimage, in other words what's shown when you don't provide it any attribute. On the contrary, `sepia_augustina_two` will take any attribute "augustina" does, and then apply the sepia effect onto the result. If you can do this:

    show augustina happy eyes_blue dress

then:

    show sepia_augustina_one happy eyes_blue dress
    # won't work, because Transform doesn't take attributes

    show sepia_augustina_two happy eyes_blue dress
    # will work, and show "augustina happy eyes_blue dress" in sepia effect

class LayeredImageProxy(name, transform=None)

  This is an image-like object that proxies attributes passed to it to another layered image.

  name

    A string giving the name of the layeredimage to proxy to.

  transform

    If given, a transform or list of transforms that are applied to the image after it has been proxied.

## Selecting attributes to display

Several factors influence what gets displayed following a given Show Statement. To provide more clarity as to what happens in which order, this section showcases the life of a set of attributes, from the show statement to the on-screen display.

- The `show` statement provides the initial set of attributes, following the image tag.

- If a `config.adjust_attributes` function exists to match the image tag, it is called, and returns a potentially different set of attributes. If so, it replaces the former set, which is forgotten.

- If a `config.default_attribute_callbacks` function exists and if its trigger conditions are met, it is called and potentially adds attributes to the set.

The previous stages are not specific to layeredimages, because it is only after this stage that renpy determines which image or layeredimage will be called to display. For that reason, the given set of attributes must lead to one, and only one, defined image (or layeredimage, Live2D...), using the behavior described in the show statement section.

- Then, the provided attributes are combined with the attributes defined in the layeredimage, discarding some previously shown attributes and conserving others. This is also the point when unrecognized attributes are detected and related errors are raised. If no such error is raised, the new attributes, along with those which were not discarded, will be recognized by renpy as the set of attributes associated with that image tag. This computing takes some of the incompatibility constraints into account, but not all. For instance incompatibilities due to attributes being in the same non-multiple group will trigger at this point in time, but the if_any/if_all/if_not clauses will not. That's why an attribute called but negated by such a clause will be considered active by renpy, and will for example become visible without having to be called again, if at some point the condition of the if_x clause is no longer fulfilled.

- If an `attribute_function` has been provided to the layeredimage, it is called with the set of remaining attributes. It returns a potentially different set of attributes.

- This set is once again confronted with the incompatibility constraints of the layeredimage, this time in full. That is the final stage, and remaining attributes are called into display.

## Advice

Use underscores in image filenames.

By default, Ren'Py's layered images use underscores to separate sections of image names. It might be tempting to use images with spaces between sections, but that could lead to problems later on.

Ren'Py has a rule that if you show an image with the exact name as one that's being shown, it's shown instead. This can bypass the layered image you defined and show the sprite directly on its own, which can lead to weird problems like a pair of eyes floating in space.

By having each sprite have a different tag from the main image, this is no longer a problem.

Cropping layers isn't necessary.

Ren'Py optimizes images by cropping them to the bounding box of the non-transparent pixels before loading them into RAM. As a result, assuming the images are being predicted properly, it generally won't improve performance or image size much to crop the images yourself.

Layered images shouldn't use data that changes at runtime.

Note that with the exception of the conditions in the `if` statement, all expressions written in a `layeredimage` block are evaluated at init time, when the layered image is first defined. This is not the case for ATL transforms for example, or for anything occurring in `config.adjust_attributes`, `config.default_attributes` or `attribute_function`, but it is the case for `format_function` which is also only called at layeredimage definition.

Choosing what syntax to use

If you want a sprite to be always visible, use either the `always` clause or the `attribute x default` syntax. `always` will require you to provide the displayable explicitly (automatic attribution using the pattern will not be available), but `attribute` will spend the "x" attribute name which will always be active for that layeredimage.

If you want it to appear depending on the attributes being passed to the layeredimage at the moment of the `show` statement, for example `show eileen happy` instead of `show eileen jeans`, use the `attribute` statement, in or out of a `group` block (or implicitly defined in an `auto` group).

If you want it to appear depending on a python variable or condition, use the `if` statement.

If you want it to depend on both (for example for `show eileen ribbon` to show either a blue or red ribbon depending on a variable, but no ribbon appearing unless you ask for it with the `ribbon` attribute), declare all versions as attributes and use a dedicated `config.adjust_attributes` function.

## Examples

Pattern and auto groups

From the following files in the images/ directory (or one of its subfolders) and written code:

    francis_base.png
    francis_face_neutral.png
    francis_face_angry.png
    francis_face_happy.png
    francis_face_very_happy.png
    francis_face annoyed.png
    francis_supersad.png

    layeredimage francis:
        attribute base default
        group face auto
            attribute neutral default
        attribute supersad:
            Solid("#00c3", xysize=(100, 100))

The `francis` layeredimage will declare the (defaulted) `base` attribute, and associate it the "francis_base" (auto-defined) image using the pattern : the layeredimage name ("francis"), the group name (none here), the variant name (none here) and the attribute name ("base"), separated with underscores.

Then, in the `face` group, the explicit `neutral` attribute gets associated the "francis_face_neutral" image, following the same pattern but using "face" as the group name and "neutral" as the attribute name.

After all explicit attributes receive their images, `face` being an `auto` group, existing images (auto-defined or not) are scanned for a match with the pattern. Here, three are found : "francis_face_angry", "francis_face_happy" and "francis_face_very_happy". They are associated with the `angry`, `happy` and `very_happy` attributes respectively, using the same pattern as before. No `annoyed` attribute is defined however, since the "francis_face annoyed" image contains a space where the pattern expected an underscore.

Finally, the `supersad` attribute is declared, but since a displayable is explicitly provided, the pattern does not look for a matching image.

The "francis_supersad" and "francis_face annoyed" images get auto-defined from the filename as part of Ren'Py's ordinary protocol, but these sprites don't find a match with any attribute or auto group, so they end up not being used in the `francis` layeredimage.

As you can see, using the pattern to associate images to attributes and using auto groups shrinks the code considerably. The same layeredimage would have taken 13 lines if everything was declared explicitly (try it!), and this syntax allows for geometric growth of the sprite set - adding any number of new faces wouldn't require any change to the code, for example.

Dynamism in attributes

Here is an example for defining attributes depending on variables (as mentioned in the Advice section):

    layeredimage eileen:
        attribute base default
        group outfit auto
        group ribbon prefix "ribbon":
            attribute red
            attribute blue

    default eileen_ribbon_color = "red"

    init python:
        def eileen_adjuster(names):
            atts = set(names[1:])
            if "ribbon" in atts:
                atts.remove("ribbon")
                atts.add("ribbon_" + eileen_ribbon_color)
            return names[0], *atts

    define config.adjust_attributes["eileen"] = eileen_adjuster