Search Docs
Loading...
Skip to content

Apply a Filter or Effect

Apply color grading, blur, pixelization, and other visual treatments to design elements using CE.SDK’s effect system, then configure, stack, and manage them with the engine API.

The same photo shown in a grid under different effects: the original, pixelization, an adjustments effect, a duotone filter, a LUT filter, and a combination of effects

9 mins
estimated time
GitHub

CE.SDK uses a single effect API for both filters and effects. Filters apply color transformations such as LUT filters and duotone, while effects apply visual modifications such as blur, pixelize, vignette, and image adjustments. Both are created with createEffect and attached to a block’s effect list, where they render in order and can be stacked, toggled, and removed individually.

Many design blocks — such as pages and graphic blocks — support effects, while others like the scene block do not. The examples below apply effects to graphic blocks with image fills, laying out the same photo in a grid so each effect can be compared against the original.

Programmatic Effect Application#

Apply, configure, and combine effects directly through the engine’s block API.

Check Effect Support#

Not every block type accepts effects, so call supportsEffects(_:) before reaching for any of the other effect APIs. It returns false for the scene block and true for a graphic block.

let sceneSupportsEffects = try engine.block.supportsEffects(scene) // false
let blockSupportsEffects = try engine.block.supportsEffects(originalCell) // true
print("scene supports effects: \(sceneSupportsEffects)")
print("graphic block supports effects: \(blockSupportsEffects)")

Create an Effect#

Create a new effect instance with createEffect(_:), passing the EffectType you want. Here we create a pixelize effect and an adjustments effect. Creating an effect does not change the rendered output yet — the effect has to be attached to a block first.

let pixelize = try engine.block.createEffect(.pixelize)
let adjustments = try engine.block.createEffect(.adjustments)

Add Effects to a Block#

Attach an effect to the end of a block’s effect list with appendEffect(_:effectID:), or place it at a specific position with insertEffect(_:effectID:index:). Use removeEffect(_:index:) to take one out of the list. Here the pixelize and adjustments effects are each appended to their own image block.

try engine.block.appendEffect(pixelizeCell, effectID: pixelize)
try engine.block.appendEffect(adjustmentsCell, effectID: adjustments)

Configure Effect Properties#

Each effect type exposes its own set of properties. List them with findAllProperties(_:), then set each value with the typed setter that matches the property — for example setFloat(_:property:value:) for amounts like brightness and contrast, and setInt(_:property:value:) for discrete values like pixel size.

let pixelizeProperties = try engine.block.findAllProperties(pixelize)
let adjustmentProperties = try engine.block.findAllProperties(adjustments)
print("pixelize properties: \(pixelizeProperties)")
print("adjustment properties: \(adjustmentProperties)")

The adjustments effect leaves the image unchanged until at least one of its properties — such as brightness — is set to a non-zero value.

try engine.block.setInt(pixelize, property: "effect/pixelize/horizontalPixelSize", value: 10)
try engine.block.setFloat(adjustments, property: "effect/adjustments/brightness", value: 0.2)
try engine.block.setFloat(adjustments, property: "effect/adjustments/contrast", value: 0.15)

Apply LUT Filters#

A LUT (look-up table) filter applies color grading by mapping each source color through a lookup-table image. Set the LUT image with setURL(_:property:value:) on effect/lut_filter/lutFileURI, match horizontalTileCount and verticalTileCount to the grid baked into that image, and control the blend with intensity (from 0.0 for the original to 1.0 for the full filter).

let lutFilter = try engine.block.createEffect(.lutFilter)
try engine.block.setURL(
lutFilter,
property: "effect/lut_filter/lutFileURI",
value: baseURL.appendingPathComponent("ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png"),
)
try engine.block.setInt(lutFilter, property: "effect/lut_filter/horizontalTileCount", value: 5)
try engine.block.setInt(lutFilter, property: "effect/lut_filter/verticalTileCount", value: 5)
try engine.block.setFloat(lutFilter, property: "effect/lut_filter/intensity", value: 0.9)
try engine.block.appendEffect(lutCell, effectID: lutFilter)

In a full editor you can also browse the available LUT filters through the asset library with engine.asset.findAssets(...) and apply them by their metadata. See Create a Custom LUT Filter for the complete LUT workflow, including the tile-count layout.

Apply Duotone Filters#

A duotone filter remaps the image’s tones onto two colors, mapping darker areas toward a dark color and lighter areas toward a light color. Set both colors with setColor(_:property:color:). The intensity property is a mixing weight from -1.0 to 1.0: positive values emphasize the light color and negative values emphasize the dark color.

let duotone = try engine.block.createEffect(.duotoneFilter)
try engine.block.setColor(
duotone,
property: "effect/duotone_filter/darkColor",
color: .rgba(r: 0.1, g: 0.2, b: 0.4, a: 1),
)
try engine.block.setColor(
duotone,
property: "effect/duotone_filter/lightColor",
color: .rgba(r: 0.95, g: 0.85, b: 0.6, a: 1),
)
try engine.block.setFloat(duotone, property: "effect/duotone_filter/intensity", value: 0.8)
try engine.block.appendEffect(duotoneCell, effectID: duotone)

Combine Multiple Effects#

Stack several effects on a single block to build a layered treatment. Effects render in the order they appear in the list, so the insertion position matters. Here the adjustments effect is inserted at index 0 so it runs before the duotone and pixelize effects.

let comboAdjustments = try engine.block.createEffect(.adjustments)
try engine.block.setFloat(comboAdjustments, property: "effect/adjustments/brightness", value: 0.2)
let comboDuotone = try engine.block.createEffect(.duotoneFilter)
try engine.block.setColor(
comboDuotone,
property: "effect/duotone_filter/lightColor",
color: .rgba(r: 0.95, g: 0.85, b: 0.6, a: 1),
)
try engine.block.setFloat(comboDuotone, property: "effect/duotone_filter/intensity", value: 0.6)
let comboPixelize = try engine.block.createEffect(.pixelize)
try engine.block.setInt(comboPixelize, property: "effect/pixelize/horizontalPixelSize", value: 6)
try engine.block.appendEffect(combinedCell, effectID: comboDuotone)
try engine.block.appendEffect(combinedCell, effectID: comboPixelize)
try engine.block.insertEffect(combinedCell, effectID: comboAdjustments, index: 0)

Managing Applied Effects#

Inspect, toggle, and remove the effects already attached to a block.

Query Applied Effects#

Use getEffects(_:) to read a block’s ordered list of effect ids. This is the entry point for building an effect-management interface or inspecting what is currently applied; the order reflects how the effects render.

let effectsList = try engine.block.getEffects(combinedCell)
print("applied effects: \(effectsList)")

Enable and Disable Effects#

Toggle an individual effect without removing it using setEffectEnabled(effectID:enabled:). Disabled effects are skipped when the block renders, while their properties stay intact for when you enable them again. Read the current state with isEffectEnabled(effectID:).

try engine.block.setEffectEnabled(effectID: comboPixelize, enabled: false)
let pixelizeEnabled = try engine.block.isEffectEnabled(effectID: comboPixelize)
print("pixelize enabled: \(pixelizeEnabled)")

Destroy Unused Effects#

Removing an effect from a block’s list does not free it. Destroy an effect you no longer need with destroy(_:), the same API used for design blocks. Effects still attached to a block are destroyed automatically when the block itself is destroyed.

try engine.block.removeEffect(combinedCell, index: 2)
try engine.block.destroy(comboPixelize)

Additional Techniques#

Patterns for applying effects at scale and packaging them for reuse.

Batch Processing#

Apply the same effect across many blocks by iterating over them. Check supportsEffects(_:) on each block before creating an effect so unsupported blocks are skipped. Here every graphic block on the page receives a small brightness lift.

let graphicBlocks = try engine.block.find(byType: .graphic)
for graphic in graphicBlocks {
guard try engine.block.supportsEffects(graphic) else { continue }
let batchAdjustments = try engine.block.createEffect(.adjustments)
try engine.block.setFloat(batchAdjustments, property: "effect/adjustments/brightness", value: 0.1)
try engine.block.appendEffect(graphic, effectID: batchAdjustments)
}

Reusable Effect Presets#

Bundle a set of effects into a reusable function to apply a consistent look — a brand filter or a film emulation — across blocks or sessions. The preset below stacks a desaturating adjustments effect with a warm duotone.

@MainActor
private func applyVintagePreset(engine: Engine, to block: DesignBlockID) throws {
let adjustments = try engine.block.createEffect(.adjustments)
try engine.block.setFloat(adjustments, property: "effect/adjustments/contrast", value: -0.15)
try engine.block.setFloat(adjustments, property: "effect/adjustments/saturation", value: -0.2)
try engine.block.appendEffect(block, effectID: adjustments)
let duotone = try engine.block.createEffect(.duotoneFilter)
try engine.block.setColor(
duotone,
property: "effect/duotone_filter/darkColor",
color: .rgba(r: 0.2, g: 0.15, b: 0.1, a: 1),
)
try engine.block.setColor(
duotone,
property: "effect/duotone_filter/lightColor",
color: .rgba(r: 0.95, g: 0.9, b: 0.75, a: 1),
)
try engine.block.setFloat(duotone, property: "effect/duotone_filter/intensity", value: 0.4)
try engine.block.appendEffect(block, effectID: duotone)
}

Effect properties are fixed values rather than keyframes. To vary an effect over time, animate the block itself with the engine’s animation APIs rather than mutating effect properties on a timer.

Performance Considerations#

Effects render on the GPU, but each one adds work to every frame the block appears in. A few guidelines keep rendering responsive:

  • Stack only the effects you need — every entry in a block’s list is evaluated on each render.
  • Color adjustments are inexpensive, while LUT filters and blurs are heavier. Favor adjustments when either would achieve the look.
  • On lower-powered devices, keep the number of simultaneous effects per block small.
  • When applying the same treatment across many blocks, reuse the same property values rather than recomputing them per block, and disable effects with setEffectEnabled(effectID:enabled:) during heavy editing instead of removing and recreating them.

Troubleshooting#

Symptom Cause Solution
Effect has no visible result The effect was created but never attached Call appendEffect or insertEffect to add it to the block’s list
Adjustments effect does nothing All adjustment properties are still at their default of zero Set a property such as effect/adjustments/brightness to a non-zero value
LUT filter has no visible result The LUT image URL does not resolve, or the tile counts do not match the image Confirm the URL loads and set horizontalTileCount and verticalTileCount to match the LUT image’s grid
Effect still renders after removal removeEffect detaches but does not destroy Call destroy on the effect to free it
Effect missing after saving and reloading a scene Effects serialize with the scene, but a referenced resource such as a LUT image may no longer resolve, or the effect was destroyed before saving Keep the lutFileURI reachable after load, and avoid destroying effects you still need
Attaching an effect throws an error The target block does not support effects Verify with supportsEffects before attaching

API Reference#

Methods#

Method Description
engine.block.supportsEffects(_:) Check whether a block supports effects
engine.block.createEffect(_:) Create a new effect instance of an EffectType
engine.block.appendEffect(_:effectID:) Add an effect to the end of a block’s effect list
engine.block.insertEffect(_:effectID:index:) Insert an effect at a specific position
engine.block.removeEffect(_:index:) Remove the effect at an index from a block’s list
engine.block.getEffects(_:) Get the ordered list of effect ids applied to a block
engine.block.findAllProperties(_:) List all property keys of an effect
engine.block.find(byType:) Find all blocks of a DesignBlockType
engine.block.setInt(_:property:value:) Set an integer effect property
engine.block.setFloat(_:property:value:) Set a floating-point effect property
engine.block.setURL(_:property:value:) Set a URL effect property such as a LUT file
engine.block.setColor(_:property:color:) Set a color effect property
engine.block.setEffectEnabled(effectID:enabled:) Enable or disable an effect
engine.block.isEffectEnabled(effectID:) Check whether an effect is enabled
engine.block.destroy(_:) Destroy an effect instance

Properties#

Property Type Description
effect/pixelize/horizontalPixelSize Int Size of the pixelization blocks
effect/adjustments/brightness Float Brightness adjustment
effect/adjustments/contrast Float Contrast adjustment
effect/lut_filter/lutFileURI URL URL of the LUT image
effect/lut_filter/horizontalTileCount Int Number of LUT tiles per row
effect/lut_filter/verticalTileCount Int Number of LUT tiles per column
effect/lut_filter/intensity Float LUT blend amount, from 0.0 to 1.0
effect/duotone_filter/darkColor Color Color mapped to shadows
effect/duotone_filter/lightColor Color Color mapped to highlights
effect/duotone_filter/intensity Float Mixing weight from -1.0 to 1.0; positive emphasizes the light color, negative the dark

Next Steps#