Extend CE.SDK with your own LUT filters by creating and registering custom filter asset sources for brand-specific color grading.

CE.SDK provides built-in LUT filters, but many applications need brand-specific color grading or custom filter collections. Custom filter asset sources let you register your own LUT filters, query them like any other asset source, and apply the selected metadata to an image block.
This guide covers how to define filter metadata, create a custom asset source, load filters from JSON configuration, query filter assets, and apply a filter to a block.
Filter Asset Metadata#
LUT filters need these properties in the meta object:
uri- URL to the LUT image file, usually a PNG.thumbUri- URL to a preview thumbnail image, not the tiled LUT atlas.horizontalTileCount- Number of horizontal tiles in the LUT grid.verticalTileCount- Number of vertical tiles in the LUT grid.blockType-EffectType.LutFilter.key, which resolves to//ly.img.ubq/effect/lut_filter.
The sample builds separate LUT and thumbnail URLs from the SDK asset base URI, then defines three filter assets with localized labels, search tags, groups, and LUT metadata.
val customFilters = listOf( Asset( id = "vintage-warm", context = AssetContext(sourceId = CUSTOM_FILTER_SOURCE_ID), label = "Vintage Warm", locale = "en", tags = listOf("vintage", "warm", "retro"), groups = listOf("Warm Tones"), meta = mapOf( "uri" to warmLutUri, "thumbUri" to warmThumbnailUri, "horizontalTileCount" to "5", "verticalTileCount" to "5", "blockType" to EffectType.LutFilter.key, ), ), Asset( id = "cool-cinema", context = AssetContext(sourceId = CUSTOM_FILTER_SOURCE_ID), label = "Cool Cinema", locale = "en", tags = listOf("cinema", "cool", "film"), groups = listOf("Cool Tones"), meta = mapOf( "uri" to monochromeLutUri, "thumbUri" to monochromeThumbnailUri, "horizontalTileCount" to "5", "verticalTileCount" to "5", "blockType" to EffectType.LutFilter.key, ), ), Asset( id = "bw-classic", context = AssetContext(sourceId = CUSTOM_FILTER_SOURCE_ID), label = "B&W Classic", locale = "en", tags = listOf("black and white", "classic", "monochrome"), groups = listOf("Monochrome"), meta = mapOf( "uri" to monochromeLutUri, "thumbUri" to monochromeThumbnailUri, "horizontalTileCount" to "5", "verticalTileCount" to "5", "blockType" to EffectType.LutFilter.key, ), ),)Create a Custom Filter Source#
Create an AssetSource when you want full control over filtering, pagination, group handling, or remote lookup behavior. The source returns FindAssetsResult values from its findAssets implementation.
private class CustomFilterAssetSource( sourceId: String, private val filters: List<Asset>,) : AssetSource(sourceId = sourceId) { override suspend fun getGroups(): List<String>? = filters.flatMap { it.groups.orEmpty() }.distinct()
override suspend fun findAssets(query: FindAssetsQuery): FindAssetsResult { val searchQuery = query.query val queryGroups = query.groups.orEmpty()
val filteredAssets = filters.filter { asset -> val matchesQuery = searchQuery.isNullOrBlank() || buildList { asset.label?.let(::add) addAll(asset.tags.orEmpty()) }.any { value -> value.contains(searchQuery, ignoreCase = true) }
val matchesGroups = queryGroups.isEmpty() || asset.groups.orEmpty().any(queryGroups::contains)
matchesQuery && matchesGroups }
val startIndex = query.page * query.perPage val pageAssets = filteredAssets.drop(startIndex).take(query.perPage) val nextPage = if (startIndex + pageAssets.size < filteredAssets.size) { query.page + 1 } else { -1 }
return FindAssetsResult( assets = pageAssets, currentPage = query.page, nextPage = nextPage, total = filteredAssets.size, ) }}Register the source with the engine before you query it. The sample removes an existing source with the same ID first so repeated runs stay deterministic.
val customSource = CustomFilterAssetSource( sourceId = CUSTOM_FILTER_SOURCE_ID, filters = customFilters,)
if (customSource.sourceId in engine.asset.findAllSources()) { engine.asset.removeSource(sourceId = customSource.sourceId)}engine.asset.addSource(source = customSource)Load Filters From JSON#
For static filter collections, load the same metadata from a JSON string with addLocalSourceFromJSON. The JSON format includes a version, the source id, and an assets array containing filter definitions.
if (JSON_FILTER_SOURCE_ID in engine.asset.findAllSources()) { engine.asset.removeSource(sourceId = JSON_FILTER_SOURCE_ID)}
val loadedJsonSourceId = engine.asset.addLocalSourceFromJSON( contentJSON = """ { "version": "2.0.0", "id": "$JSON_FILTER_SOURCE_ID", "assets": [ { "id": "sunset-glow", "label": { "en": "Sunset Glow" }, "tags": { "en": ["warm", "sunset", "golden"] }, "groups": ["Warm Tones"], "meta": { "uri": "$warmLutUri", "thumbUri": "$warmThumbnailUri", "horizontalTileCount": "5", "verticalTileCount": "5", "blockType": "${EffectType.LutFilter.key}" } }, { "id": "ocean-breeze", "label": { "en": "Ocean Breeze" }, "tags": { "en": ["cool", "blue", "ocean"] }, "groups": ["Cool Tones"], "meta": { "uri": "$monochromeLutUri", "thumbUri": "$monochromeThumbnailUri", "horizontalTileCount": "5", "verticalTileCount": "5", "blockType": "${EffectType.LutFilter.key}" } } ] } """.trimIndent(),)For hosted filter catalogs, call the contentUri overload of addLocalSourceFromJSON. Use absolute URLs in the JSON or write paths with the {{base_url}}/... placeholder so CE.SDK replaces the placeholder with the catalog’s base path.
Query and Apply Filters#
Use findAssets to query your custom source, group-filtered results, or the source created from JSON.
val customFilterResults = engine.asset.findAssets( sourceId = CUSTOM_FILTER_SOURCE_ID, query = FindAssetsQuery(page = 0, perPage = 10),)
val warmToneFilters = engine.asset.findAssets( sourceId = CUSTOM_FILTER_SOURCE_ID, query = FindAssetsQuery(page = 0, perPage = 10, groups = listOf("Warm Tones")),)
val jsonFilterResults = engine.asset.findAssets( sourceId = JSON_FILTER_SOURCE_ID, query = FindAssetsQuery(page = 0, perPage = 10),)To apply a LUT filter, create EffectType.LutFilter, copy the LUT metadata into the effect properties, set the intensity, and append the effect to a block that supports effects.
val filterAsset = warmToneFilters.assets.first()val filterMeta = filterAsset.meta ?: error("Filter asset ${filterAsset.id} is missing metadata.")
require(engine.block.supportsEffects(imageBlock)) { "The selected block must support effects before applying a LUT filter."}
val lutEffect = engine.block.createEffect(type = EffectType.LutFilter)engine.block.setString( block = lutEffect, property = "effect/lut_filter/lutFileURI", value = filterMeta["uri"] ?: error("Filter asset ${filterAsset.id} is missing meta.uri."),)engine.block.setInt( block = lutEffect, property = "effect/lut_filter/horizontalTileCount", value = filterMeta["horizontalTileCount"]?.toInt() ?: error("Filter asset ${filterAsset.id} is missing meta.horizontalTileCount."),)engine.block.setInt( block = lutEffect, property = "effect/lut_filter/verticalTileCount", value = filterMeta["verticalTileCount"]?.toInt() ?: error("Filter asset ${filterAsset.id} is missing meta.verticalTileCount."),)engine.block.setFloat( block = lutEffect, property = "effect/lut_filter/intensity", value = 0.85F,)engine.block.appendEffect(block = imageBlock, effectBlock = lutEffect)Export the Result#
After applying the filter, export the affected page or block with the regular block export API.
val exportedImage = engine.block.export(block = page, mimeType = MimeType.PNG)Troubleshooting#
Filters Not Found in Query#
- Verify that the source is registered before calling
findAssets. - Check that the source ID in
findAssetsmatches the ID you registered or loaded from JSON. - Include labels, tags, and groups that match your search and filtering logic.
LUT Not Rendering Correctly#
- Verify that
horizontalTileCountandverticalTileCountmatch the actual LUT image grid. - Confirm that the LUT URI is reachable from the app and from export.
- Store LUT images as PNG files to avoid compression artifacts.
JSON Source Not Loading#
- Verify that the JSON includes
version,id, andassets. - Keep all metadata values as strings.
- Ensure each filter asset includes
uri,thumbUri, tile counts, andblockType.
API Reference#
| Method | Description |
|---|---|
AssetSource.findAssets(query=_) |
Return matching filter assets from a custom source. |
AssetSource.getGroups() |
Return the available filter groups for group-based queries. |
engine.asset.addSource(source=_) |
Register a custom asset source. |
engine.asset.addLocalSourceFromJSON(contentJSON=_) |
Create a local asset source from inline JSON. |
engine.asset.addLocalSourceFromJSON(contentUri=_) |
Create a local asset source from a JSON URI. |
engine.asset.findAssets(sourceId=_, query=_) |
Query assets from a registered source. |
engine.asset.findAllSources() |
Return all registered asset source IDs. |
engine.asset.removeSource(sourceId=_) |
Remove a registered asset source by ID. |
engine.block.supportsEffects(block=_) |
Check whether a block can render an effect stack. |
engine.block.createEffect(type=EffectType.LutFilter) |
Create a LUT filter effect block. |
engine.block.setString(block=_, property="effect/lut_filter/lutFileURI", value=_) |
Set the LUT image URI. |
engine.block.setInt(block=_, property="effect/lut_filter/horizontalTileCount", value=_) |
Set the horizontal LUT tile count. |
engine.block.setInt(block=_, property="effect/lut_filter/verticalTileCount", value=_) |
Set the vertical LUT tile count. |
engine.block.setFloat(block=_, property="effect/lut_filter/intensity", value=_) |
Set the filter intensity. |
engine.block.appendEffect(block=_, effectBlock=_) |
Attach the configured effect to a block. |
engine.block.export(block=_, mimeType=MimeType.PNG) |
Export the filtered result. |
Key Types#
| Type | Purpose |
|---|---|
Asset |
Represents a filter returned from findAssets. |
FindAssetsQuery |
Defines pagination, search, and group filters for asset queries. |
FindAssetsResult |
Contains the returned assets and pagination metadata. |
EffectType.LutFilter |
Type-safe Android effect constant for LUT filters. |
Next Steps#
Now that you understand how to create and register custom filter sources, explore related topics:
- Create a Custom LUT Filter - Understand LUT image format and create your own color grading filters.
- Blur Effects - Add blur effects to images and videos.