Search Docs
Loading...
Skip to content

Source Sets

Configure source sets for images and videos so CE.SDK can choose an appropriate resolution for editing previews and exports.

Source Sets result exported from the Android guide

7 mins
estimated time
GitHub

Source sets let you provide multiple versions of the same media asset at different pixel dimensions. The engine can load a smaller source for preview rendering and still use a higher-resolution source when the target drawing or export size needs it.

This guide covers source sets on image fills, source sets inside asset definitions, video source sets, and the video preview quality setting that helps keep editing responsive.

How Source Set Selection Works#

When CE.SDK renders a fill, it calculates the current drawing size in screen pixels. If the fill has a source set, the engine selects the source with the closest size that meets or exceeds the drawing size. If every source is smaller than the drawing size, the largest source is used.

Without a source set, image fills decode the single image source and clamp the maximum edge length to the maxImageSize setting. Source sets give you explicit control over the intermediate resolutions used during editing and export.

Setting a Source Set on an Image Fill#

Set image source sets on the image fill block with engine.block.setSourceSet(). Each Source contains a Uri, width, and height, and the dimensions must match the actual media file.

val imageBlock = engine.block.create(DesignBlockType.Graphic)
engine.block.setShape(imageBlock, shape = engine.block.createShape(ShapeType.Rect))
engine.block.setWidth(imageBlock, value = 300F)
engine.block.setHeight(imageBlock, value = 300F)
engine.block.setPositionX(imageBlock, value = 50F)
engine.block.setPositionY(imageBlock, value = 50F)
val imageFill = engine.block.createFill(FillType.Image)
engine.block.setSourceSet(
block = imageFill,
property = "fill/image/sourceSet",
sourceSet = listOf(
Source(
uri = Uri.parse("https://img.ly/static/ubq_samples/sample_1_512x341.jpg"),
width = 512,
height = 341,
),
Source(
uri = Uri.parse("https://img.ly/static/ubq_samples/sample_1_1024x683.jpg"),
width = 1024,
height = 683,
),
),
)
engine.block.setFill(block = imageBlock, fill = imageFill)
engine.block.appendChild(parent = page, child = imageBlock)

Querying and Modifying Source Sets#

Use engine.block.getSourceSet() to inspect the sources stored on a fill. When you need to add an image and do not already know its dimensions, call engine.block.addImageFileUriToSourceSet() so the engine loads the image and records its width and height.

val currentImageSourceSet = engine.block.getSourceSet(
block = imageFill,
property = "fill/image/sourceSet",
)
engine.block.addImageFileUriToSourceSet(
block = imageFill,
property = "fill/image/sourceSet",
uri = "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg",
)
val updatedImageSourceSet = engine.block.getSourceSet(
block = imageFill,
property = "fill/image/sourceSet",
)

setSourceSet() is still the better choice when your backend already knows the dimensions because it avoids fetching the file just to read metadata.

Using Source Sets in Asset Definitions#

Asset definitions can carry the same responsive sources in AssetPayload.sourceSet. After you add the asset to a local source and apply it, engine.asset.defaultApplyAsset() configures the resulting block’s fill with the source set from the asset payload.

val assetSourceId = "android-guide-source-sets"
if (assetSourceId in engine.asset.findAllSources()) {
engine.asset.removeSource(assetSourceId)
}
val assetWithSourceSet = AssetDefinition(
id = "multi-resolution-image",
label = mapOf("en" to "Multi-resolution image"),
meta = mapOf(
"kind" to "image",
"fillType" to FillType.Image.key,
),
payload = AssetPayload(
sourceSet = listOf(
Source(
uri = Uri.parse("https://img.ly/static/ubq_samples/sample_1_512x341.jpg"),
width = 512,
height = 341,
),
Source(
uri = Uri.parse("https://img.ly/static/ubq_samples/sample_1_1024x683.jpg"),
width = 1024,
height = 683,
),
Source(
uri = Uri.parse("https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg"),
width = 2048,
height = 1366,
),
),
),
)
engine.asset.addLocalSource(
sourceId = assetSourceId,
supportedMimeTypes = listOf(MimeType.JPEG.key),
)
engine.asset.addAsset(sourceId = assetSourceId, asset = assetWithSourceSet)
val asset = engine.asset.fetchAsset(
sourceId = assetSourceId,
assetId = assetWithSourceSet.id,
) ?: error("Expected the local source to return the asset.")
val assetBlock = engine.asset.defaultApplyAsset(asset)
?: error("Expected the image asset to create a block.")
val assetFill = engine.block.getFill(assetBlock)
val assetSourceSet = engine.block.getSourceSet(
block = assetFill,
property = "fill/image/sourceSet",
)

Use this pattern when your asset library stores the available resolutions alongside each asset. The applied block then behaves like a block you configured manually with setSourceSet().

Video Source Sets#

Video fills use the fill/video/sourceSet property. The source selection rules match image fills: CE.SDK chooses the closest source for the current drawing size and can use the highest-quality source for export.

val videoBlock = engine.block.create(DesignBlockType.Graphic)
engine.block.setShape(videoBlock, shape = engine.block.createShape(ShapeType.Rect))
engine.block.setWidth(videoBlock, value = 400F)
engine.block.setHeight(videoBlock, value = 225F)
engine.block.setPositionX(videoBlock, value = 50F)
engine.block.setPositionY(videoBlock, value = 400F)
val videoFill = engine.block.createFill(FillType.Video)
engine.block.setSourceSet(
block = videoFill,
property = "fill/video/sourceSet",
sourceSet = listOf(
Source(
uri = Uri.parse("https://img.ly/static/example-assets/sourceset/1x.mp4"),
width = 720,
height = 1280,
),
),
)
engine.block.setFill(block = videoBlock, fill = videoFill)
engine.block.appendChild(parent = page, child = videoBlock)
engine.block.addVideoFileUriToSourceSet(
block = videoFill,
property = "fill/video/sourceSet",
uri = "https://img.ly/static/example-assets/sourceset/2x.mp4",
)
val videoSourceSet = engine.block.getSourceSet(
block = videoFill,
property = "fill/video/sourceSet",
)

Use engine.block.addVideoFileUriToSourceSet() when the engine should load a video file to determine its dimensions before adding it to the existing source set.

Video Preview Quality Settings#

For editing performance, enable features/forceLowQualityVideoPreview to force video previews to use the smallest available source. Export still uses the highest-quality source available for the requested output.

engine.editor.setSettingBoolean(
keypath = "features/forceLowQualityVideoPreview",
value = true,
)

The related features/matchThumbnailSourceToFill setting controls whether video thumbnails follow the fill’s selected source. When it is disabled, which is the default, thumbnails use the smallest source.

Automatic Source Sets#

The engine can make the smaller entries of a source set by itself, so it can draw a small image where the full one is not needed. This is off by default. Set features/automaticSourceSetsEnabled to true to turn it on.

Which fills get entries#

The engine adds entries to an image fill when both of these are true:

  • You added the fill in this session. A fill from a loaded scene gets none, so a document of many pages costs nothing to open.
  • The fill has one image and no smaller sizes. An imageFileURI on its own counts, and so does a source set with a single entry.

A fill that already holds two or more entries gets none. You gave the engine sizes to choose from, so it adds no more.

The engine adds entries one time for each fill. Two fills of the same image file share the same entries, so the engine does the work one time.

Give a fill a different image and the engine removes the entries it made, because they show the old image. The new image gets none.

How many entries, and how large#

The smallest entry the engine makes is 512 pixels on the long edge. It makes one entry for each time the long edge of the image halves down to that size. The sizes shrink by the same ratio each step, and the last one lands on 512 pixels:

Long edge of the image Entries the engine adds
Under 1024 pixels none
1024 pixels one, at 512 pixels
2048 pixels two, at 1024 and 512 pixels
4096 pixels three, at 2048, 1024 and 512 pixels

An image under 1024 pixels on its long edge gets nothing, because the single entry it could hold saves too little to pay for the work.

No entry is larger than the maxImageSize setting, which is 4096 pixels by default. The engine draws no image above that size, so a larger entry would never be selected. Your own image stays the largest source in the set, so an export still uses the full resolution.

The engine reads an image with more pixels than maxImageSize squared at a smaller size, and makes each entry from the entry before it.

The format of an entry#

Each entry is a JPEG for an image without transparency, and a PNG for one with it. A JPEG entry uses quality 90, which you cannot change, so it saves space but is not exact.

What the work costs#

The engine takes one step for each engine update, reads the image a few rows at a time, and encodes each entry row by row, so adding an image does not hold a frame. No step runs while you drag, during an export, or during an implicit update.

Saving a scene that holds generated entries#

The entries live in memory. Each one states a buffer:// address, which means nothing once the session that made it is gone. findAllTransientResources lists every resource of that kind, the entries among them. What you must do depends on how you save:

An archive needs nothing. saveToArchive carries the bytes of every resource, the entries included, so it holds no buffer:// address that a later session cannot resolve.

A string needs somewhere to put the bytes. A saved string can only state an address, and buffer is not one of the schemes a save allows, so saveToString reports the disallowed scheme and stops. The Android SaveToStringOptions has no persistence callback yet, so do it yourself before the save: read the bytes of each entry with engine.editor.getBufferData, store them where your application keeps its media, and put the new addresses into the source set with setSourceSet.

Troubleshooting#

Problem Solution
Wrong resolution is selected Check that every Source.width and Source.height matches the actual file dimensions.
Preview performance is poor Add smaller sources to the source set, especially for video fills or large images.
Export quality is too low Include a source that is large enough for the target export resolution.
The asset applies without a source set Confirm that AssetPayload.sourceSet is populated and that the applied asset uses an image or video fill.

API Reference#

API Description
engine.block.create(blockType=DesignBlockType.Graphic) Create a graphic block that can display a fill.
engine.block.createShape(type=ShapeType.Rect) Create a rectangular shape for the graphic block.
engine.block.setShape(block=_, shape=_) Attach the shape to a graphic block.
engine.block.setWidth(block=_, value=_) Set the block width.
engine.block.setHeight(block=_, value=_) Set the block height.
engine.block.setPositionX(block=_, value=_) Set the block’s horizontal position.
engine.block.setPositionY(block=_, value=_) Set the block’s vertical position.
engine.block.appendChild(parent=_, child=_) Add a block to the scene or page hierarchy.
engine.block.createFill(fillType=FillType.Image) Create an image fill block.
engine.block.createFill(fillType=FillType.Video) Create a video fill block.
engine.block.setFill(block=_, fill=_) Attach a fill to a design block.
engine.block.getFill(block=_) Read the fill attached to a design block.
engine.block.setSourceSet(block=_, property="fill/image/sourceSet", sourceSet=_) Assign responsive image sources.
engine.block.getSourceSet(block=_, property="fill/image/sourceSet") Read responsive image sources.
engine.block.addImageFileUriToSourceSet(block=_, property="fill/image/sourceSet", uri=_) Add an image source after loading its dimensions.
engine.block.setSourceSet(block=_, property="fill/video/sourceSet", sourceSet=_) Assign responsive video sources.
engine.block.getSourceSet(block=_, property="fill/video/sourceSet") Read responsive video sources.
engine.block.addVideoFileUriToSourceSet(block=_, property="fill/video/sourceSet", uri=_) Add a video source after loading its dimensions.
engine.asset.findAllSources() List registered asset source IDs.
engine.asset.removeSource(sourceId=_) Remove a previously registered local asset source.
engine.asset.addLocalSource(sourceId=_, supportedMimeTypes=_) Create a local asset source.
engine.asset.addAsset(sourceId=_, asset=_) Add an asset definition to a local source.
engine.asset.fetchAsset(sourceId=_, assetId=_) Fetch the asset result to apply.
engine.asset.defaultApplyAsset(asset=_) Apply an asset and configure the created block from its payload.
engine.editor.setSettingBoolean(keypath="features/forceLowQualityVideoPreview", value=_) Force video previews to use the smallest source while editing.

Source#

Property Type Description
uri Uri Image or video resource URI.
width Int Source width in pixels.
height Int Source height in pixels.

Next Steps#

  • Image Fills - Apply photos and responsive image sources to design blocks.
  • Video Fills - Apply video content and responsive video sources to design blocks.
  • Export - Export designs using the best matching source for the target output.