Search Docs
Loading...
Skip to content

Programmatic Creation

Build compositions entirely through code using the CE.SDK Engine for automation, batch processing, and headless rendering.

10 mins
estimated time
GitHub

CE.SDK provides a complete Engine API for building designs through code. Instead of relying on user interactions through an editor UI, you can create scenes, add blocks like text, images, and shapes, and position them programmatically. This approach enables automation workflows, batch processing, headless rendering, and integration with custom interfaces.

This guide covers how to create a scene structure with social media dimensions, set background colors, add text with mixed styling, line shapes, images, and export the finished composition.

Initialize the Engine#

The runnable sample owns an Engine instance because it renders the composition without opening an editor UI. Inside the main-dispatcher export function, initialize CE.SDK with your Application, start the engine with your license and user ID, and bind an offscreen render surface for headless rendering.

The page dimensions and ExportOptions(targetWidth = 1080F, targetHeight = 1080F) shown later control the exported PNG dimensions.

Engine.init(application)
val currentEngine = Engine.getInstance(id = "ly.img.engine.example")
engine = currentEngine
engineStarted = currentEngine.start(license = license, userId = userId)
currentEngine.bindOffscreen(width = 1080, height = 1080)

Create Scene Structure#

We create the foundation of the composition with social media dimensions (1080x1080 pixels for Instagram). A scene contains one or more pages, and pages contain the design blocks.

val scene = engine.scene.create()
val page = engine.block.create(DesignBlockType.Page)
engine.block.setWidth(page, value = 1080F)
engine.block.setHeight(page, value = 1080F)
engine.block.appendChild(parent = scene, child = page)

engine.scene.create() returns a scene handle. Create a page with engine.block.create(DesignBlockType.Page), set its dimensions with setWidth() and setHeight(), then attach it to the scene with appendChild().

Set Page Background#

We set the page background using a color fill. This demonstrates how to create and assign fills to blocks.

val backgroundFill = engine.block.createFill(FillType.Color)
engine.block.setFill(block = page, fill = backgroundFill)
engine.block.setFillSolidColor(
block = page,
color = Color.fromRGBA(r = 0.94F, g = 0.93F, b = 0.98F, a = 1F),
)

We create a color fill using createFill(FillType.Color), assign it to the page with setFill(), then set the solid fill color on the target block via setFillSolidColor(block=_, color=_).

Add Text Blocks#

Text blocks allow you to add and style text content. We demonstrate three different approaches to text sizing and styling.

Select the Font Variant#

Before styling text, define a Typeface with the variants the sample needs and select the regular font by weight and style. The bold and italic toggles can only apply variants that exist in this typeface.

val robotoBase = "https://cdn.img.ly/assets/v3/ly.img.typeface/fonts/Roboto"
val robotoTypeface = Typeface(
name = "Roboto",
fonts = listOf(
Font(
uri = Uri.parse("$robotoBase/Roboto-Regular.ttf"),
subFamily = "Regular",
weight = FontWeight.NORMAL,
style = FontStyle.NORMAL,
),
Font(
uri = Uri.parse("$robotoBase/Roboto-Bold.ttf"),
subFamily = "Bold",
weight = FontWeight.BOLD,
style = FontStyle.NORMAL,
),
Font(
uri = Uri.parse("$robotoBase/Roboto-Italic.ttf"),
subFamily = "Italic",
weight = FontWeight.NORMAL,
style = FontStyle.ITALIC,
),
Font(
uri = Uri.parse("$robotoBase/Roboto-BoldItalic.ttf"),
subFamily = "Bold Italic",
weight = FontWeight.BOLD,
style = FontStyle.ITALIC,
),
),
)
val robotoRegular = robotoTypeface.fonts.first {
it.weight == FontWeight.NORMAL && it.style == FontStyle.NORMAL
}

Create Text and Set Content#

Create a text block, set its content with replaceText(), then bind the selected Roboto font and typeface:

val headline = engine.block.create(DesignBlockType.Text)
engine.block.replaceText(headline, text = "Integrate\nCreative Editing\ninto your App")
engine.block.setFont(headline, fontFileUri = robotoRegular.uri, typeface = robotoTypeface)
engine.block.setTextLineHeight(headline, lineHeight = 0.78F)

Style Entire Text Block#

Apply styling to the entire text block using toggleBoldFont() and setTextColor():

if (engine.block.canToggleBoldFont(headline)) {
engine.block.toggleBoldFont(headline)
}
engine.block.setTextColor(headline, color = Color.fromRGBA(r = 0F, g = 0F, b = 0F, a = 1F))

Enable Automatic Font Sizing#

Configure the text block to automatically scale its font size to fit within fixed dimensions:

engine.block.setWidthMode(headline, mode = SizeMode.ABSOLUTE)
engine.block.setHeightMode(headline, mode = SizeMode.ABSOLUTE)
engine.block.setWidth(headline, value = 960F)
engine.block.setHeight(headline, value = 300F)
// The Android binding has no typed helper for this text option yet.
engine.block.setBoolean(headline, property = "text/automaticFontSizeEnabled", value = true)

Android exposes the block sizing modes through typed APIs. The automatic font-size switch uses the generic Boolean property API because no public typed Android setter exists for text/automaticFontSizeEnabled yet.

Range-based Text Styling#

Apply different styles to specific character ranges within a single text block:

engine.block.setTextColor(
tagline,
color = Color.fromRGBA(r = 0.2F, g = 0.2F, b = 0.8F, a = 1F),
from = 0,
to = 9,
)
if (engine.block.canToggleItalicFont(tagline, from = 0, to = 9)) {
engine.block.toggleItalicFont(tagline, from = 0, to = 9)
}
engine.block.setTextColor(
tagline,
color = Color.fromRGBA(r = 0F, g = 0F, b = 0F, a = 1F),
from = 10,
to = 21,
)
if (engine.block.canToggleBoldFont(tagline, from = 10, to = 21)) {
engine.block.toggleBoldFont(tagline, from = 10, to = 21)
}

Android’s range-based overloads take start-inclusive and end-exclusive UTF-16 code unit indices ([from, to)):

  • setTextColor(block, color, from, to) - apply color to a specific UTF-16 range
  • canToggleBoldFont(block, from, to) / toggleBoldFont(block, from, to) - toggle bold styling for a range
  • canToggleItalicFont(block, from, to) / toggleItalicFont(block, from, to) - toggle italic styling for a range

Fixed Font Size#

Set an explicit font size with setTextFontSize() instead of using automatic sizing:

val ctaTitle = engine.block.create(DesignBlockType.Text)
engine.block.replaceText(ctaTitle, text = "Start a Free Trial")
engine.block.setFont(ctaTitle, fontFileUri = robotoRegular.uri, typeface = robotoTypeface)
engine.block.setTextFontSize(ctaTitle, fontSize = 80F)
engine.block.setTextLineHeight(ctaTitle, lineHeight = 1F)

Add Shapes#

We create shapes using graphic blocks. CE.SDK supports Rect, Line, Ellipse, Polygon, Star, and VectorPath shapes through ShapeType object constants.

Create a Shape Block#

Create a graphic block and assign a shape to it:

val dividerLine = engine.block.create(DesignBlockType.Graphic)
val lineShape = engine.block.createShape(ShapeType.Line)
engine.block.setShape(block = dividerLine, shape = lineShape)

Apply Fill to Shape#

Create a color fill, assign it to the graphic block, then set the line color with setFillSolidColor(block=_, color=_):

val lineFill = engine.block.createFill(FillType.Color)
engine.block.setFill(block = dividerLine, fill = lineFill)
engine.block.setFillSolidColor(
block = dividerLine,
color = Color.fromRGBA(r = 0F, g = 0F, b = 0F, a = 1F),
)

Add Images#

We add images using graphic blocks with image fills.

Create an Image Block#

Create a graphic block with a rect shape and an image fill:

val logo = engine.block.create(DesignBlockType.Graphic)
val logoShape = engine.block.createShape(ShapeType.Rect)
engine.block.setShape(block = logo, shape = logoShape)
val logoFill = engine.block.createFill(FillType.Image)
// Image fills currently expose their URI through the generic property API.
engine.block.setUri(
block = logoFill,
property = "fill/image/imageFileURI",
value = Uri.parse("https://img.ly/static/ubq_samples/imgly_logo.jpg"),
)
engine.block.setFill(block = logo, fill = logoFill)

We set the image URL with setUri() and pass the image URI to the fill/image/imageFileURI property. Image fills currently expose this URI through the generic property API instead of a dedicated typed setter.

Position and Size Blocks#

All blocks use the same positioning and sizing APIs:

engine.block.setContentFillMode(logo, mode = ContentFillMode.CONTAIN)
engine.block.setWidth(logo, value = 200F)
engine.block.setHeight(logo, value = 65F)
engine.block.setPositionX(logo, value = 820F)
engine.block.setPositionY(logo, value = 960F)
engine.block.appendChild(parent = page, child = logo)
  • setWidth() / setHeight() - set block dimensions
  • setPositionX() / setPositionY() - set block position
  • setContentFillMode() - control how content fills the block (ContentFillMode.CONTAIN, ContentFillMode.COVER, ContentFillMode.CROP)
  • appendChild() - add the block to the page hierarchy

Export the Composition#

We export the finished composition using the engine API.

Export Using the Engine API#

engine.block.export() returns the rendered bytes as a ByteBuffer:

val exportOptions = ExportOptions(targetWidth = 1080F, targetHeight = 1080F)
// Ensure remote font files and the image fill are ready before the offscreen export.
engine.block.forceLoadResources(listOf(page, headline, tagline, ctaTitle, ctaUrl, logo))
val blob = engine.block.export(page, mimeType = MimeType.PNG, options = exportOptions)

The sample preloads the page and resource-bearing text and image blocks before export, so remote font files and image fills are resolved before the offscreen renderer captures the PNG.

Write to File System#

Write the returned ByteBuffer to disk from an IO dispatcher. The sample copies the remaining bytes into a ByteArray, writes them to a temporary PNG file, and returns that file for verification.

return withContext(Dispatchers.IO) {
val outputFile = File.createTempFile("composition-${UUID.randomUUID()}", ".png")
val bytes = ByteArray(blob.remaining())
blob.get(bytes)
outputFile.outputStream().use { output ->
output.write(bytes)
}
outputFile
}

Clean Up Resources#

The sample tracks whether Engine.start() started the named Engine instance. Inside finally, it only stops the Engine when this call owns that running instance:

if (engineStarted) {
engine?.stop()
}

This ensures resources started by the sample are released even if scene creation, export, or file writing fails.

API Reference#

APICategoryPurpose
engine.scene.create()SceneCreate a scene for programmatic composition.
engine.block.create(blockType=_)BlockCreate pages, text blocks, and graphic blocks.
engine.block.appendChild(parent=_, child=_)BlockAttach a block to the scene or page hierarchy.
engine.block.setWidth(block=_, value=_)LayoutSet a block width.
engine.block.setHeight(block=_, value=_)LayoutSet a block height.
engine.block.setWidthMode(block=_, mode=_)LayoutSet how a block’s width is resolved.
engine.block.setHeightMode(block=_, mode=_)LayoutSet how a block’s height is resolved.
engine.block.setPositionX(block=_, value=_)LayoutSet a block’s horizontal position.
engine.block.setPositionY(block=_, value=_)LayoutSet a block’s vertical position.
engine.block.createFill(fillType=_)FillCreate a color or image fill.
engine.block.setFill(block=_, fill=_)FillAssign a fill to a block.
engine.block.setFillSolidColor(block=_, color=_)FillSet a solid fill color on a block.
engine.block.createShape(type=_)ShapeCreate a shape for a graphic block.
engine.block.setShape(block=_, shape=_)ShapeAssign a shape to a graphic block.
engine.block.replaceText(block=_, text=_)TextSet text content.
engine.block.setFont(block=_, fontFileUri=_, typeface=_)TextBind a typeface and font file to a text block.
engine.block.setTextFontSize(block=_, fontSize=_)TextSet a fixed font size.
engine.block.setTextLineHeight(block=_, lineHeight=_)TextSet the line height multiplier for text paragraphs.
engine.block.setTextColor(block=_, color=_)TextSet text color for the full text block.
engine.block.setTextColor(block=_, color=_, from=_, to=_)TextSet text color for a character range.
engine.block.setBoolean(block=_, property="text/automaticFontSizeEnabled", value=_)TextEnable or disable automatic font sizing through the generic property API.
engine.block.canToggleBoldFont(block=_)TextCheck whether bold styling can be toggled.
engine.block.toggleBoldFont(block=_)TextToggle bold styling for the full text block.
engine.block.canToggleBoldFont(block=_, from=_, to=_)TextCheck whether bold styling can be toggled for a range.
engine.block.toggleBoldFont(block=_, from=_, to=_)TextToggle bold styling for a character range.
engine.block.canToggleItalicFont(block=_, from=_, to=_)TextCheck whether italic styling can be toggled for a range.
engine.block.toggleItalicFont(block=_, from=_, to=_)TextToggle italic styling for a character range.
engine.block.setUri(block=_, property="fill/image/imageFileURI", value=_)ImageSet the image URI on an image fill.
engine.block.setContentFillMode(block=_, mode=_)ImageControl how image content fits the block.
engine.block.forceLoadResources(blocks=_)ExportResolve referenced fonts and image fills before exporting.
engine.block.export(block=_, mimeType=_, options=_)ExportExport the page to image bytes.
Engine.init(application=_)EngineInitialize CE.SDK before creating an Engine instance.
engine.stop()EngineRelease resources owned by the Engine instance.

Troubleshooting#

  • Blocks not appearing: Verify that appendChild() attaches blocks to the page. Blocks must be part of the scene hierarchy to render.
  • Engine not initialized: Call Engine.init(application) before Engine.getInstance(...), typically from Application.onCreate().
  • Text styling not applied: Verify ranges are correct for range-based APIs. Android uses start-inclusive and end-exclusive UTF-16 code unit indices for the selected range.
  • Image stretched: Use setContentFillMode(block, ContentFillMode.CONTAIN) to maintain the image’s aspect ratio.
  • Export fails: Verify that page dimensions are set before export. The export requires valid dimensions.
  • Typeface missing variants: If canToggleBoldFont() or canToggleItalicFont() returns false, check that the configured Typeface includes the matching weight or style.
  • Resources remain active: Call engine.stop() in finally when the sample starts its own Engine instance.

Next Steps#