Skip to content

Images, preview and evaluate

A Rive runtime can reference an image that is not inside the file. LIGR marks that image unresolved. The graphic renders without it. Supply the image to make the graphic complete.

Find the unresolved assets first.

Terminal
api GET "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID" | jq '.stateMachine.assets[] | select(.unresolved)'

Create an upload session. Send the exact byte count and the lowercase SHA-256 of the file.

Terminal
api POST "themes/$THEME_ID/rive-graphics/images" \
--data '{"filename":"crowd.png","size":48213,"sha256":"6c1b…","contentType":"image/png"}'

The response holds fileId, url, expiresAt and headers. Upload the exact bytes with one PUT. Send each header from headers unchanged. x-amz-checksum-sha256 is the base64 form of the SHA-256 digest. It is not the hex value you sent.

Terminal
curl -X PUT "$URL" -H "content-type: image/png" -H "x-amz-checksum-sha256: $CHECKSUM" --data-binary @crowd.png

Then supply the file id to the asset.

Terminal
api PUT "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID/assets/crowd.png/file" --data '{"fileId":901}'

The asset stops being unresolved. The response holds the full stateMachine. The same fileId works as defaultFileId on an asset exposure.

Supported types are PNG, JPEG and WebP. The limit is 25 MB. The upload session belongs to the theme. Use the same theme that owns the graphic. The builder shows the same control. Open the Assets tab and select Upload image on a row marked Missing image.

Every Image that a view-model property binds needs an embedded placeholder asset in the Rive file. An Image with no asset can blank the whole artboard in the web runtime. LIGR replaces the embedded asset at runtime, so any small image works as the placeholder.

LIGR sends a bound or exposed image to the Rive runtime at its own pixel size. LIGR does not resize it to the placeholder.

The Rive runtime draws an Image outside a layout at the pixel size of the image it holds. The scale of the Image node then multiplies that size. The placeholder size has no effect after LIGR replaces the image.

For example, an Image node with scale 2 draws a 256×256 crest at 512×512. The same node draws a 1000×1000 crest at 2000×2000. Crests from different sources then draw at different sizes in the same slot.

Put each replaceable Image inside a layout of the slot size, and set fit on the Image. The runtime then scales every image into the layout box. A 200×200 crest and a 1000×1000 crest draw at the same size.

scene.rml
<ImageAsset id="0:40" file="assets/crest.png" name="crest" />
<!-- inside the Artboard -->
<LayoutComponent name="Home crest slot" x="400" y="100" width="96" height="96" styleId="0:51" id="0:50">
<LayoutComponentStyle widthUnitsValue="points" heightUnitsValue="points" id="0:51" />
<Image name="Home crest" assetId="0:40" fit="contain" id="0:52" />
</LayoutComponent>

Use contain to show the full image, or cover to fill the box and crop the edges. Bind the view-model image property to Home crest as usual, or expose the crest asset.

If the Image cannot sit in a layout, keep the node scale at 1. Then set an image transform on the data binding with a fixed output size:

Terminal
api PUT "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID/data-bindings/Main.homeCrest/image-transform" \
--data '{"fit":"contain","outWidth":96,"outHeight":96}'

LIGR resizes each image to 96×96 before the runtime draws it. An image transform applies to data bindings only. An exposed asset without a binding needs the layout.

An exposed image makes one image slot on each entity of its type. For example, an image exposed with entityType: "team" adds a slot to every team.

The binding expression returns the id of the entity, for example $d.1.id for team one. LIGR then shows the image that the operator uploaded for that entity.

An operator uploads the image in the dashboard. Open the team page and select the Customize tab. Player pages and competition pages have the same control.

If the entity has no upload, the graphic shows the default file of the exposure. Set the default file with defaultFileId on the exposure.

Terminal
api GET "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID/preview?scenario=Ace%20on%20First%20Serve"

The response holds one signed URL for the runtime file and one for each stored image. Every URL expires after five minutes. scenario returns the demonstration data sequence of that name. Omit scenario to receive null.

List the scenario names of a sport with GET /v2/scenarios/{sport}.

Terminal
api POST "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID/evaluate" \
--data '{"scenario":"Ace on First Serve","variables":{"stage":"FINAL"}}'

The response holds one row per data binding in bindings and one row per user expression in userExpressions, plus unresolved, the count of rows that failed. mode says what the server did. full means the server produced a value for each binding. static means the server checked syntax and references only. A static response holds no value.

Today this route runs in static mode only. It checks that each binding and user expression parses, and that every $v and $u reference names a control variable or user expression that exists. It never runs the expression, so it never reports a value. To see the computed values, open the graphic in the dashboard Graphics Builder and select a match. The builder preview renders the graphic with the data of that match.

Evaluate is a POST, so it needs themes:write.

Terminal
ligr-graphic rive asset file crowd.png ./crowd.png
ligr-graphic rive asset expose crowd.png --name "Crowd" --entity team --default-file ./crowd.png
ligr-graphic rive preview --scenario "Ace on First Serve" --open
ligr-graphic rive eval --scenario "Ace on First Serve" --var stage=FINAL
ligr-graphic rive push --watch

rive eval exits 0 only when every binding and user expression resolves. Use it in a pipeline. rive push --watch applies rive.json again after each save. Press Ctrl+C to stop.