Apply a Template to a Scene - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/scene-apply-template?language=kotlin&platform=android#setup # Apply a Template to a Scene In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to apply the contents of a given template scene to the currently loaded scene through the `scene` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Applying Template Scenes ``` suspend fun applyTemplate(template: String) ``` Applies the contents of the given template scene to the currently loaded scene. This loads the template scene while keeping the design unit and page dimensions of the current scene. The content of the pages is automatically adjusted to fit the new dimensions. * `template`: the template scene file contents, a base64 string. ``` suspend fun applyTemplate(templateUri: Uri) ``` Applies the contents of the given template scene to the currently loaded scene. This loads the template scene while keeping the design unit and page dimensions of the current scene. The content of the pages is automatically adjusted to fit the new dimensions. * `templateUri`: the resource of the template scene file. ``` val rawResId = R.raw.cesdk_postcard_1 val rawResourcePath = context.resources.run { ContentResolver.SCHEME_ANDROID_RESOURCE + "://"+ getResourcePackageName(rawResId) + "/"" + getResourceTypeName(rawResId) + "/" + getResourceEntryName(rawResId) } engine.scene.applyTemplate(template = "UBQ1ewoiZm9ybWF0Ij...") engine.scene.applyTemplate(templateUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")) engine.scene.applyTemplate(templateUri = Uri.parse("file:///android_asset/templates/cesdk_postcard_1.scene")) engine.scene.applyTemplate(templateUri = Uri.fromFile(File(filesDir, "templates/cesdk_postcard_1.scene"))) engine.scene.applyTemplate(templateUri = Uri.parse(rawResourcePath)) ``` ``` fun get(): DesignBlock? ``` Return the currently active scene. * Returns the scene or null, if none was created yet. ``` engine.scene.get() ``` Integrate the Mobile Editor - CE.SDK | IMG.LY Docs [android/composable/kotlin] https://img.ly/docs/cesdk/mobile-editor/quickstart?framework=composable&language=kotlin&platform=android#adding-dependency # Integrate the Mobile Editor In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile editor in your Android app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) ## Adding dependency Add IMG.LY maven repository to the list of maven urls in the `settings.gradle` file. ``` maven { name "IMG.LY Artifactory" url "https://artifactory.img.ly/artifactory/maven" mavenContent { includeGroup("ly.img") } } ``` Add editor dependency in the `build.gradle` file of your application module. ``` implementation "ly.img:editor:1.43.0" // Add this as well if you want to use img.ly camera's powerful features. // If not, then system camera will be used everywhere. implementation "ly.img:camera:1.43.0" ``` ## Requirements In order to use the mobile editor, your application should meet the following requirements: * `buildFeatures.compose` should be `true`, as the editor is written in Jetpack Compose. ``` buildFeatures { compose true } ``` * `composeOptions.kotlinCompilerExtensionVersion` should match the kotlin version. Use the official compatibility map in [here](https://developer.android.com/jetpack/androidx/releases/compose-kotlin). ``` composeOptions { kotlinCompilerExtensionVersion = "1.5.3" } ``` * `compose-bom` version is `2023.05.01` or higher if your project uses Jetpack Compose dependencies. Note that using lower versions may cause crashes and issues in your own compose code, as our version will override yours. In case you are not using BOM, you can find the BOM to compose library version mapping in [here](https://developer.android.com/jetpack/compose/bom/bom-mapping). ``` implementation(platform("androidx.compose:compose-bom:2023.05.01")) ``` * Kotlin version is 1.9.10 or higher. * `minSdk` is 24 (Android 7) or higher. ``` minSdk 24 ``` By default, the mobile editor supports following ABIs: `arm64-v8a`, `armeabi-v7a`, `x86_64` and `x86`. If you want to filter out some of the ABIs, use `abiFilters`. ``` ndk { abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" } ``` ## Usage In this example, the basic usage of the mobile editor is going to be demonstrated using the [Design Editor](/docs/cesdk/mobile-editor/solutions/design-editor/) solution, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). In order to launch the editor, an `EngineConfiguration` object should be provided. Use the `EngineConfiguration.rememberForDesign()` for the simplest implementation. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. #### Warning Other than the _userId_ we also use the [Settings.Secure.ANDROID\_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) for better data accuracy. You should include it in the _Data safety form_ of your application when uploading it to the Play Store. ``` val engineConfiguration = EngineConfiguration.rememberForDesign( license = "", userId = "", ) ``` Afterwards, invoke `DesignEditor` composable function from your composable context. Other than the `EngineConfiguration`, you should also provide an `onClose` callback as the last parameter. That defines what should happen when the editor close event is triggered. ``` DesignEditor(engineConfiguration = engineConfiguration) { // You can set result here navController.popBackStack() } ``` That is all. Check all the available [solutions](/docs/cesdk/mobile-editor/solutions/) in order to decide which solution fits you best. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-editor/configuration/). Source Sets - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/source-sets?language=kotlin&platform=android#drawing # Source Sets Source sets allow specifying an entire set of sources, each with a different size, that should be used for drawing a block. The appropriate source is then dynamically chosen based on the current drawing size. This allows using the same scene to render a preview on a mobile screen using a small image file and a high-resolution file for print in the backend. This guide will show you how to specify source sets both for existing blocks and when defining assets. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Source+Sets&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Source+Sets&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets) ### Drawing When an image needs to be drawn, the current drawing size in screen pixels is calculated and the engine looks up the most appropriate source file to draw at that resolution. 1. If a source set is set, the source with the closest size exceeding the drawing size is used 2. If no source set is set, the full resolution image is downscaled to a maximum edge length of 4096 (configurable via `maxImageSize` setting) and drawn to the target area This also gives more control about up- and downsampling to you, as all intermediate resolutions can be generated using tooling of your choice. **Source sets are also used during export of your designs and will resolve to the best matching asset for the export resolution.** ## Setup the scene We first create a new scene with a new page. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) engine.scene.zoomToBlock( block = page, paddingLeft = 50F, paddingTop = 50F, paddingRight = 50F, paddingBottom = 50F, ) ``` ## Using a Source Set for an existing Block To make use of a source set for an existing image fill, we use the `setSourceSet` API. This defines a set of sources and specifies height and width for each of these sources. The engine then chooses the appropriate source during drawing. You may query an existing source set using `getSourceSet`. You can add sources to an existing source set with `addImageFileURIToSourceSet`. ``` val block = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(block, engine.block.createShape(ShapeType.Rect)) 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, ), Source( uri = Uri.parse("https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg"), width = 2048, height = 1366, ), ), ) engine.block.setFill(block = block, fill = imageFill) engine.block.appendChild(parent = page, child = block) ``` ## Using a Source Set in an Asset For assets, source sets can be defined in the `payload.sourceSet` field. This is directly translated to the `sourceSet` property when applying the asset. The resulting block is configured in the same way as the one described above. The code demonstrates how to add an asset that defines a source set to a local source and how `applyAsset` handles a populated `payload.sourceSet`. ``` val assetWithSourceSet = AssetDefinition( id = "my-image", meta = mapOf( "kind" to "image", "fillType" to "//ly.img.ubq/fill/image", ), 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, ), ), ), ) ``` ## Video Source Sets Source sets can also be used for video fills. This is done by setting the `sourceSet` property of the video fill. The engine will then use the source with the closest size exceeding the drawing size. Thumbnails will use the smallest source if `features/matchThumbnailSourceToFill` is disabled, which is the default. For low end devices or scenes with large videos, you can force the preview to always use the smallest source when editing by enabling `features/forceLowQualityVideoPreview`. On export, the highest quality source is used in any case. ``` 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.addVideoFileUriToSourceSet( block = videoFill, property = "fill/video/sourceSet", uri = "https://img.ly/static/example-assets/sourceset/2x.mp4", ) ``` Text - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-text?language=kotlin&platform=android#setup # Text In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to edit ranges within text blocks. A list of all available settings can be found on the [Settings](/docs/cesdk/engine/api/editor-settings/) page. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` fun replaceText( block: DesignBlock, text: String, from: Int = -1, to: Int = -1, ) ``` Inserts the given text into the selected range of the text block. Required scope: "text/edit" * `block`: the text block into which to insert the given text. * `text`: the text which should replace the selected range in the block. * `from`: the start index of the range that should be replaced. If the value is negative, this will fall back to the start of the entire text range. * `to`: the index after the last character that should be replaced by the inserted text. If the value is negative, this will fall back to the end of the entire text range. ``` engine.block.replaceText(text, "Hello World") ``` ``` fun removeText( block: DesignBlock, from: Int = -1, to: Int = -1, ) ``` Removes selected range of text of the given text block. Required scope: "text/edit" * `block`: the text block from which the selected text should be removed. * `from`: the start index of the range that should be removed. If the value is negative, this will fall back to the start of the entire text range. * `to`: the index after the last character that should be removed. If the value is negative, this will fall back to the end of the entire text range. ``` engine.block.removeText(text, from = 0, to = 6) ``` ``` fun setTextColor( block: DesignBlock, color: Color, from: Int = -1, to: Int = -1, ) ``` Changes the color of the text in the selected range to the given color. Required scope: "fill/change" * `block`: the text block whose color should be changed. * `color`: the new color of the selected text range. * `from`: the start index of the range whose color should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character whose color should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextColor(text, Color.fromHex("#FFFF0000"), from = 1, to = 4) ``` ``` fun getTextColors( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the ordered unique list of colors of the text in the selected range. * `block`: the text block whose colors should be returned. * `from`: the start index of the range whose colors should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose colors should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the ordered unique list of colors of the text in the selected range. ``` val colorsInRange = engine.block.getTextColors(text, from = 2, to = 5) ``` ``` fun setTextFontWeight( block: DesignBlock, fontWeight: FontWeight, from: Int = -1, to: Int = -1, ) ``` Changes the weight of the text in the selected range to the given weight. Required scope: "text/character" * `block`: the text block whose weight should be changed. * `fontWeight`: the new weight of the selected text range. * `from`: the start index of the UTF-16 range whose weight should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the UTF-16 index after the last grapheme whose weight should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextFontWeight(text, fontWeight = FontWeight.BOLD, from = 0, to = 5) ``` ``` fun getTextFontWeights( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the ordered unique list of font weights of the text in the selected range. * `block`: the text block whose font weights should be returned. * `from`: the start index of the range whose font weights should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose font weights should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the ordered unique list of font weights of the text in the selected range. ``` val fontWeights = engine.block.getTextFontWeights(text) ``` ``` fun setTextFontSize( block: DesignBlock, fontSize: Float, from: Int = -1, to: Int = -1, ) ``` Changes the size of the text in the selected range to the given size. If the font size is applied to the entire text block, its font size property will be updated. Required scope: "text/character" * `block`: the text block whose size should be changed. * `fontStyle`: the new size of the selected text range. * `from`: the start index of the UTF-16 range whose size should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the UTF-16 index after the last grapheme whose size should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` val fontWeights = engine.block.setTextFontSize(text, fontSize: 14, from = 2, to = 5) ``` ``` fun getTextFontSizes( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the ordered unique list of font sizes of the text in the selected range. * `block`: the text block whose font sizes should be returned. * `from`: the start index of the range whose font sizes should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose font sizes should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the ordered unique list of font sizes of the text in the selected range. ``` val fontWeights = engine.block.getTextFontSizes(text) ``` ``` fun setTextFontStyle( block: DesignBlock, fontStyle: FontStyle, from: Int = -1, to: Int = -1, ) ``` Changes the style of the text in the selected range to the given style. Required scope: "text/character" * `block`: the text block whose style should be changed. * `fontStyle`: the new style of the selected text range. * `from`: the start index of the UTF-16 range whose style should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the UTF-16 index after the last grapheme whose style should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextFontStyle(text, fontStyle = FontStyle.ITALIC, from = 0, to = 5) ``` ``` fun getTextFontStyles( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the ordered unique list of font styles of the text in the selected range. * `block`: the text block whose font styles should be returned. * `from`: the start index of the range whose font styles should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose font styles should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the ordered unique list of font styles of the text in the selected range. ``` val fontStyles = engine.block.getTextFontStyles(text) ``` ``` fun setTextFontStyle( block: DesignBlock, fontStyle: FontStyle, from: Int = -1, to: Int = -1, ) ``` Changes the style of the text in the selected range to the given style. Required scope: "text/character" * `block`: the text block whose style should be changed. * `fontStyle`: the new style of the selected text range. * `from`: the start index of the UTF-16 range whose style should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the UTF-16 index after the last grapheme whose style should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextFontStyle(text, fontStyle = FontStyle.ITALIC, from = 0, to = 5) ``` ``` fun getTextFontStyles( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the ordered unique list of font styles of the text in the selected range. * `block`: the text block whose font styles should be returned. * `from`: the start index of the range whose font styles should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose font styles should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the ordered unique list of font styles of the text in the selected range. ``` val fontStyles = engine.block.getTextFontStyles(text) ``` ``` fun setTextCase( block: DesignBlock, textCase: TextCase, from: Int = -1, to: Int = -1, ) ``` Sets the given text case for the selected range of text. Required scope: "text/character" * `block`: the text block whose text case should be changed. * `textCase`: the new text case value. * `from`: the start index of the range whose text case should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character whose text case should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextCase(text, textCase = TextCase.TITLE_CASE) ``` ``` fun getTextCases( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the ordered list of text cases of the text in the selected range. * `block`: the text block whose text cases should be returned. * `from`: the start index of the range whose text cases should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose text cases should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the ordered list of text cases of the text in the selected range. ``` val textCases = engine.block.getTextCases() ``` ``` fun canToggleBoldFont( block: DesignBlock, from: Int = -1, to: Int = -1, ): Boolean ``` Returns whether the font weight of the given block can be toggled between bold and normal. * `block`: the text block block whose font weight should be toggled. * `from`: the start index of the range whose whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character whose whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` val canToggleBoldFont = engine.block.canToggleBoldFont(text) ``` ``` fun canToggleItalicFont( block: DesignBlock, from: Int = -1, to: Int = -1, ): Boolean ``` Returns whether the font style of the given block can be toggled between italic and normal. * `block`: the text block block whose font style should be toggled. * `from`: the start index of the range whose text case should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character whose text case should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` val canToggleItalicFont = engine.block.canToggleItalicFont(text) ``` ``` fun toggleBoldFont( block: DesignBlock, from: Int = -1, to: Int = -1, ) ``` Toggles the font weight of the given block between bold and normal. Required scope: "text/character" * `block`: the text block whose font weight should be toggled. * `from`: the start index of the range whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.toggleBoldFont(text) ``` ``` fun toggleItalicFont( block: DesignBlock, from: Int = -1, to: Int = -1, ) ``` Toggles the font style of the given block between italic and normal. Required scope: "text/character" * `block`: the text block whose font style should be toggled. * `from`: the start index of the range whose text case should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character whose text case should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.toggleItalicFont(text) ``` ``` fun setFont( block: DesignBlock, fontFileUri: Uri, typeface: Typeface, ) ``` Sets the given font and typeface for the text block. Existing formatting is reset. Required scope: "text/character" * `block`: the text block whose font should be changed. * `fontFileUri`: the Uri of the new font file. * `typeface`: the typeface of the new font. ``` val typefaceAssetResults = engine.asset.findAssets( sourceId = "ly.img.typeface", query = FindAssetsQuery( query = "Open Sans", page = 0, perPage = 100 ) ) val typeface = typefaceAssetResults.assets[0].payload.typeface val font = typeface.fonts.first { font -> font.subFamily == "Bold" } engine.block.setFont(text, font.uri, typeface) ``` ``` fun setTypeface( block: DesignBlock, typeface: Typeface, from: Int = -1, to: Int = -1, ) ``` Sets the given typeface for the text block. The current formatting, e.g., bold or italic, is retained as far as possible. Some formatting might change if the new typeface does not support it, e.g. thin might change to light, bold to normal, and/or italic to non-italic. If the typeface is applied to the entire text block, its typeface property will be updated. If a run does not support the new typeface, it will fall back to the default typeface from the typeface property. Required scope: "text/character" * `block`: the text block whose font should be changed. * `typeface`: the new typeface. * `from`: the start index of the range whose typeface should be set. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose typeface should be set. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTypeface(text, typeface, from = 0, to = 5) engine.block.setTypeface(text, typeface) ``` ``` fun getTypeface(block: DesignBlock): Typeface ``` Returns the typeface property of the text block. Does not return the typefaces of the text runs. * `block`: the text block whose typeface should be returned. * Returns the typeface of the text block or throws exception if the typeface is unknown. ``` val defaultTypeface = engine.block.getTypeface(text) ``` ``` fun getTypefaces( block: DesignBlock, from: Int = -1, to: Int = -1, ): List ``` Returns the typefaces of the text block. * `block`: the text block whose typefaces should be returned. * `from`: the start index of the range whose typefaces should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: the index after the last character of the range whose typefaces should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns the typefaces of the text block or throws exception if the typeface is unknown. ``` val typefaces = engine.block.getTypefaces(text) ``` ``` fun getTextCursorRange(): IntRange? ``` Returns the indices of the selected range of the text block that is currently being edited. If both the start and end index of the returned range have the same value, then the text cursor is positioned at that index. * Returns the selected grapheme range or null if no text block is currently being edited. ``` val range = engine.block.getTextCursorRange() ``` ``` fun getTextVisibleLineCount(block: DesignBlock): Int ``` Returns the number of visible lines in the given text block. * `block`: the text block whose line count should be returned. * Returns the number of lines of text in the block. ``` val lineCount = engine.block.getTextVisibleLineCount(text) ``` ``` fun getTextLineBoundingBoxRect( block: DesignBlock, index: Int, ): RectF ``` Returns the bounds of the visible area of the given line of the text block. The values are in the scene's global coordinate space (which has its origin at the top left). * `block`: the text block whose line bounding box should be returned. * `index`: the index of the line whose bounding box should be returned. * Returns the bounding box of the line. ``` val lineBoundingBox = engine.block.getTextLineBoundingBoxRect(text, 0) ``` Using the Photo Editor - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/solutions/photo-editor?language=kotlin&platform=android#configuration # Using the Photo Editor `Photo Editor` is built to provide versatile photo editing capabilities. Toggling from edit and preview modes enables users to evaluate their edited photo before export. A dock at the bottom of the editor provides quick access to the most essential photo editing tools allowing users to tweak adjustments, crop the photo, and add filters, effects, blur, text, shapes, as well as stickers. In this example, we will show you how to initialize the `Photo Editor` solution for the mobile editor on Android. The mobile editor is implemented entirely with Jetpack Compose and this example assumes that you use compose navigation, however, you can check the `Activity` and `Fragment` implementation samples on the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page. They can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-photo-editor/PhotoEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-photo-editor/PhotoEditorSolution.kt) ## Configuration In order to launch the `Photo Editor`, you should initialize `EngineConfiguration` object. It is responsible for the configuration of the [Engine](/docs/cesdk/engine/quickstart/). All the properties other than the `onCreate` property have default values, so you do not have to worry about initializing them unless you need. Moreover, there is an `EngineConfiguration.rememberForPhoto` helper function that only requires the `license` property and the `imageUri` that should be used to load the scene. Note that you can override the `size` of the image that should be loaded to the scene. If skipped, scene will be loaded with the original dimensions of the image. ``` val engineConfiguration = EngineConfiguration.rememberForPhoto( license = "", imageUri = Uri.parse("https://img.ly/static/ubq_samples/sample_4.jpg"), imageSize = SizeF(1080F, 1920F), userId = "", ) ``` Optionally, you can also provide an `EditorConfiguration` object if you want to configure the UI behavior of the `Photo Editor`. If you do not provide a custom implementation, the editor will be launched with `EditorConfiguration.getDefault()` configuration object. ``` val editorConfiguration = EditorConfiguration.rememberForPhoto() ``` For more details on how to use the configuration objects, visit [configuration](/docs/cesdk/mobile-editor/configuration/) page. ## Initialization After creating the configuration objects, launching the editor is straightforward. Just invoke the `PhotoEditor` composable function from your composable context and pass the configuration objects that you have created. In addition, you also need to provide an implementation for the `onClose` callback. That defines what should happen when the editor close event is triggered. If you are not using compose navigation, you can check `Activity` and `Fragment` implementation samples in the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page for `onClose` implementations. ``` PhotoEditor( engineConfiguration = engineConfiguration, editorConfiguration = editorConfiguration, ) { // You can set result here navController.popBackStack() } ``` Kind - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-kind?language=kotlin&platform=android#setup # Kind In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a and query the kind property of design blocks through the `block` API. The `kind` of a design block is a custom string that can be assigned to a block in order to categorize it and distinguish it from other blocks that have the same type. The user interface can then customize its appearance based on the kind of the selected blocks. It can also be used for automation use cases in order to process blocks in a different way based on their kind. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` fun setKind( block: DesignBlock, kind: String, ) ``` Set the kind of the given block, fails if the block is invalid. * `block`: the block to modify. * `kind`: the block kind. ``` engine.block.setKind(text, kind = "title") ``` ``` fun getKind(block: DesignBlock): String ``` Get the kind of the given block, fails if the block is invalid. * `block`: the block to query. * Returns the block kind. ``` val kind = engine.block.getKind(text) ``` ``` fun findByKind(blockKind: String): List ``` Finds all blocks with the given kind. * `blockKind`: the kind to search for. * Returns a list of block ids. ``` val allTitles = engine.block.findByKind(blockKind = "title") ``` Placeholder - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-placeholder/?language=kotlin&platform=android#content-top # Placeholder In this example, we will demonstrate how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage placeholder behavior and controls through the block Api. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Placeholder Behavior and Controls ``` fun supportsPlaceholderBehavior(block: DesignBlock): Boolean ``` Query whether the block supports placeholder behavior. * `block`: the block to query. * Returns whether the block supports placeholder behavior. ``` if (engine.block.supportsPlaceholderBehavior(block)) { ``` ``` fun setPlaceholderBehaviorEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the placeholder behavior for a block. * `block`: the block whose placeholder behavior should be enabled or disabled. * `enabled`: Whether the placeholder behavior should be enabled or disabled. ``` engine.block.setPlaceholderBehaviorEnabled(block, enabled = true) ``` ``` fun isPlaceholderBehaviorEnabled(block: DesignBlock): Boolean ``` Query whether the placeholder behavior for a block is enabled. * `block`: the block whose placeholder behavior state should be queried. * Returns the enabled state of the block's placeholder behavior. ``` val placeholderBehaviorIsEnabled = engine.block.isPlaceholderBehaviorEnabled(block) ``` ``` fun setPlaceholderEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the placeholder function for a block. * `block`: the block whose placeholder function should be enabled or disabled. * `enabled`: whether the function should be enabled or disabled. ``` engine.block.setPlaceholderEnabled(block, enabled = true) ``` ``` fun isPlaceholderEnabled(block: DesignBlock): Boolean ``` Query whether the placeholder function for a block is enabled. * `block`: the block whose placeholder function state should be queried. * Returns the enabled state of the placeholder function. ``` val placeholderIsEnabled = engine.block.isPlaceholderEnabled(block) ``` ``` fun supportsPlaceholderControls(block: DesignBlock): Boolean ``` Checks whether the block supports placeholder controls. * `block`: The block to query. * Returns whether the block supports placeholder controls. ``` if (engine.block.supportsPlaceholderControls(block)) { ``` ``` fun setPlaceholderControlsOverlayEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the visibility of the placeholder overlay pattern for a block. * `block`: The block whose placeholder overlay should be enabled or disabled. * `enabled`: Whether the placeholder overlay should be shown or not. ``` engine.block.setPlaceholderControlsOverlayEnabled(block, enabled = true) ``` ``` fun isPlaceholderControlsOverlayEnabled(block: DesignBlock): Boolean ``` Query whether the placeholder overlay pattern for a block is shown. * `block`: The block whose placeholder overlay visibility state should be queried. * Returns the visibility state of the block's placeholder overlay pattern. ``` val overlayEnabled = engine.block.isPlaceholderControlsOverlayEnabled(block) ``` ``` fun setPlaceholderControlsButtonEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the visibility of the placeholder button for a block. * `block`: The block whose placeholder button should be shown or not. * `enabled`: Whether the placeholder button should be shown or not. ``` engine.block.setPlaceholderControlsButtonEnabled(block, enabled = true) ``` ``` fun isPlaceholderControlsButtonEnabled(block: DesignBlock): Boolean ``` Query whether the placeholder button for a block is shown. * `block`: The block whose placeholder button visibility state should be queried. * Returns the visibility state of the block's placeholder button. ``` val buttonEnabled = engine.block.isPlaceholderControlsButtonEnabled(block) ``` Scopes - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-scopes?language=kotlin&platform=android#setup # Scopes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine `block` API to modify a block's scopes. For a more high-level introduction, check the corresponding [guide](/docs/cesdk/engine/guides/scopes/). Scopes describe different aspects of a block, e.g. layout or style and can be enabled or disabled for every single block. There's also the option to control a scope globally. When configuring a scope globally you can set an override to always allow or deny a certain type of manipulation for every block. Or you can configure the global scope to defer to the individual block scopes. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun findAllScopes(): List ``` Gets all available global scopes that can be set. * Returns the list of all available global scopes. ``` // Store a list of all available global scopes val scopes = engine.editor.findAllScopes() ``` ``` fun setGlobalScope( key: String, globalScope: GlobalScope, ) ``` Set a scope to be globally allowed, denied, or deferred to the block-level. * `key`: the scope to set. * `globalScope`: `GlobalScope.ALLOW` will always allow the scope, `GlobalScope.DENY` will always deny the scope, and `GlobalScope.DEFER` will defer to the block-level. ``` // Let the global scope defer to the block-level. engine.editor.setGlobalScope(key = "layer/move", globalScope = GlobalScope.DEFER) // Manipulation of layout properties of any block will fail at this point. try { engine.block.setPositionX(image, value = 100F) // Not allowed } catch(exception: Exception) { exception.printStackTrace() } ``` ``` fun getGlobalScope(key: String): GlobalScope ``` Query the state of a global scope. * `key`: the scope to query. * Returns `GlobalScope.ALLOW` if the scope is allowed, `GlobalScope.DENY` if it is denied, and `GlobalScope.DEFER` if it is deferred to the block-level. ``` // This will return `GlobalScope.DEFER`. engine.editor.getGlobalScope(key = "layer/move") ``` ``` fun isScopeEnabled( block: DesignBlock, key: String, ): Boolean ``` Query whether a scope is enabled for a given block. * `block`: the block whose scope state should be queried. * `key`: the scope to query. * Returns the enabled state of the scope for the given block. ``` // Verify that the "layer/move" scope is now enabled for the image block. engine.block.isScopeEnabled(image, key = "layer/move") ``` ``` fun setScopeEnabled( block: DesignBlock, key: String, enabled: Boolean, ) ``` Enable or disable a scope for a given block. * `block`: the block whose scope should be enabled or disabled. * `key`: the scope to enable or disable. * `enabled`: whether the scope should be enabled or disabled. ``` // Allow the user to control the layout properties of the image block. engine.block.setScopeEnabled(image, key = "layer/move", enabled = true) // Manipulation of layout properties of any block is now allowed. try { engine.block.setPositionX(image, value = 100F) // Allowed } catch(exception: Exception) { exception.printStackTrace() } ``` ``` fun isAllowedByScope( block: DesignBlock, key: String, ): Boolean ``` Check if a scope is allowed for a given block. * `block`: the block to check. * `key`: the scope to check. * Returns whether the scope is allowed for the given block. ``` // This will return true as well since the global scope is set to `GlobalScope.DEFER`. engine.block.isAllowedByScope(image, key = "layer/move") ``` Interacting with the Scene using the CreativeEditor SDK Engine - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/blocks?language=kotlin&platform=android#creating-or-using-an-existing-scene # Interacting with the Scene using the CreativeEditor SDK Engine A block (or design block) is the main building unit in CE.SDK. Blocks are organized in a hierarchy through parent-child relationships. A scene is a specialized block that acts as the root of this hierarchy. Commonly, a scene contains several pages which in turn contain any other blocks such as images and texts. In this example, we want to show you how to create and modify a scene and its blocks in CE.SDK. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-modifying-scenes/ModifyingScenes.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-modifying-scenes/ModifyingScenes.kt) ## Creating or Using an Existing Scene When using the Engine's API in the context of the CE.SDK editor, there's already an existing scene. You can obtain a handle to this scene by calling the [SceneAPI](/docs/cesdk/engine/api/scene/)'s `fun get(): DesignBlock?` method. However, when using the Engine on its own you first have to create a scene, e.g. using `fun create(): DesignBlock`. See the [Creating Scenes](/docs/cesdk/engine/guides/create-scene/) guide for more details and options. ``` // In engine only mode we have to create our own scene and page. if (engine.scene.get() == null) { val scene = engine.scene.create() ``` Next, we need a page to place our blocks on. The scene automatically arranges its pages either in a vertical (the default) or horizontal layout. Again in the context of the editor, there's already an existing page. To fetch that page call the [BlockAPI](/docs/cesdk/engine/api/block/)'s `fun findByType(blockType: DesignBlockType): List` method and use the first element of the returned list. When only using the engine, you have to create a page yourself and append it to the scene. To do that create the page using `fun fun create(): DesignBlock` and append it to the scene with `fun appendChild(parent: DesignBlock, child: DesignBlock)`. ``` val page = engine.block.create(DesignBlockType.Page) engine.block.appendChild(parent = scene, child = page) } // Find all pages in our scene. val pages = engine.block.findByType(DesignBlockType.Page) // Use the first page we found. val page = pages.first() ``` At this point, you should have a handle to an existing scene as well as a handle to its page. Now it gets interesting when we start to add different types of blocks to the scene's page. ## Modifying the Scene As an example, we create a graphic block using the [BlockAPI](/docs/cesdk/engine/api/block/)'s `create()` method which we already used for creating our page. Then we set a rect shape and an image fill to this newly created block to give it a visual representation. To see what other kinds of blocks are available see the [Block Types](/docs/cesdk/engine/api/block-types/) in the API Reference. ``` // Create a graphic block and add it to the scene's page. val block = engine.block.create(DesignBlockType.Graphic) val fill = engine.block.createFill(FillType.Image) engine.block.setShape(block, shape = engine.block.createShape(ShapeType.Rect)) engine.block.setFill(block = block, fill = fill) ``` We set a property of our newly created image fill by giving it a URL to reference an image file from. We also make sure the entire image stays visible by setting the block's content fill mode to `'Contain'`. To learn more about block properties check out the [Block Properties](/docs/cesdk/engine/api/block-properties/) API Reference. ``` engine.block.setString( block = fill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/imgly_logo.jpg", ) // The content fill mode 'Contain' ensures the entire image is visible. engine.block.setEnum( block = block, property = "contentFill/mode", value = "Contain", ) ``` And finally, for our image to be visible we have to add it to our page using `appendChild`. ``` engine.block.appendChild(parent = page, child = block) ``` To frame everything nicely and put it into view we direct the scene's camera to zoom on our page. ``` // Zoom the scene's camera on our page. engine.scene.zoomToBlock(page) ``` Using a custom URI resolver - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/resolve-custom-uri?language=kotlin&platform=android#warning # Using a custom URI resolver CE.SDK gives you full control over how Uris should be resolved. To register a custom resolver, use `setUriResolver` and pass in a function implementing your resolution logic. If a custom resolver is set, any Uri (both relative and absolute) requested by the engine is passed through the resolver. The Uri your logic returns is then fetched by the engine. The resolved Uri is just used for the current request and not stored. If, and only if, no custom resolver is set, the engine performs the default behaviour: absolute uri objects are unchanged and relative objects are turned into android asset Uris. #### Warning Your custom Uri resolver must return an absolute Uri. ​ Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-uri-resolver/UriResolver.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-uri-resolver/UriResolver.kt) We can preview the effects of setting a custom Uri resolver with the function `fun getAbsoluteUri(uri: Uri): Uri`. Before setting a custom Uri resolver, the default behavior is as before: absolute Uri is unchanged and relative is turned into android asset Uri. ``` // This will return uri to "banana.jpg" asset file engine.editor.getAbsoluteUri(uri = Uri.parse("banana.jpg")) // This will return uri to remote resource "https://example.com/orange.png" engine.editor.getAbsoluteUri(uri = Uri.parse("https://example.com/orange.png")) ``` To show that the resolver can be fairly free-form, in this example we register a custom Uri resolver that replaces all `.jpg` images with our company logo. Note: you can still access the default Uri resolver by calling `fun defaultUriResolver(uri: Uri): Uri`. ``` // Replace all .jpg files with the IMG.LY logo! engine.editor.setUriResolver { if (it.toString().endsWith(".jpg")) { Uri.parse("https://img.ly/static/ubq_samples/imgly_logo.jpg") } else { engine.editor.defaultUriResolver(it) } } ``` Given the same uri as earlier, the custom resolver transforms it as specified. Note that after a custom resolver is set, uris that the resolver does not transform remain unmodified thanks to `defaultUriResolver`. ``` // The custom resolver will return a path to the IMG.LY logo because the given path ends with ".jpg". // This applies regardless if the given path is relative or absolute. engine.editor.getAbsoluteUri(uri = Uri.parse("banana.jpg")) // The custom resolver will not modify this path because it ends with ".png". engine.editor.getAbsoluteUri(uri = Uri.parse("https://example.com/orange.png")) // Because a custom resolver is set, relative paths that the resolver does not transform remain unmodified! engine.editor.getAbsoluteUri(uri = Uri.parse("/orange.png")) ``` To remove a previously set custom resolver, call the function with a `null` value. The Uri resolution is now back to the default behavior. ``` // Removes the previously set resolver. engine.editor.setUriResolver(null) // Since we"ve removed the custom resolver, this will return // Uri.Asset("banana.jpg") like before. engine.editor.getAbsoluteUri(uri = Uri.parse("banana.jpg")) ``` Using the Postcard Editor - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/solutions/postcard-editor?language=kotlin&platform=android#configuration # Using the Postcard Editor `Postcard Editor` is built to facilitate optimal post- & greeting- card design, from changing accent colors and selecting fonts to custom messages and pictures. Toggling from edit to preview mode allows reviewing the design in the context of the entire post/greeting card. A floating action button at the bottom of the editor features only the most essential editing options in order of relevance allowing users to overlay text, add images, shapes, stickers and upload new image assets. In this example, we will show you how to initialize the `Postcard Editor` solution for the mobile editor on Android. The mobile editor is implemented entirely with Jetpack Compose and this example assumes that you use compose navigation, however, you can check the `Activity` and `Fragment` implementation samples on the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page. They can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-postcard-editor/PostcardEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-postcard-editor/PostcardEditorSolution.kt) ## Configuration In order to launch the `Postcard Editor`, you should initialize `EngineConfiguration` object. It is responsible for the configuration of the [Engine](/docs/cesdk/engine/quickstart/). All the properties other than the `onCreate` property have default values, so you do not have to worry about initializing them unless you need. Moreover, there is an `EngineConfiguration.rememberForPostcard` helper function that only requires the `license` property. Note that you can override the `scene` that should be loaded in the `Postcard Editor` if you use the helper function. ``` val engineConfiguration = EngineConfiguration.rememberForPostcard( license = "", userId = "", ) ``` Optionally, you can also provide an `EditorConfiguration` object if you want to configure the UI behavior of the `Postcard Editor`. If you do not provide a custom implementation, the editor will be launched with `EditorConfiguration.getDefault()` configuration object. ``` val editorConfiguration = EditorConfiguration.rememberForPostcard() ``` For more details on how to use the configuration objects, visit [configuration](/docs/cesdk/mobile-editor/configuration/) page. ## Initialization After creating the configuration objects, launching the editor is straightforward. Just invoke the `PostcardEditor` composable function from your composable context and pass the configuration objects that you have created. In addition, you also need to provide an implementation for the `onClose` callback. That defines what should happen when the editor close event is triggered. If you are not using compose navigation, you can check `Activity` and `Fragment` implementation samples in the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page for `onClose` implementations. ``` PostcardEditor( engineConfiguration = engineConfiguration, editorConfiguration = editorConfiguration, ) { // You can set result here navController.popBackStack() } ``` Load Scenes From a Blob - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/load-scene-from-blob?language=kotlin&platform=android#warning # Load Scenes From a Blob In this example, we will show you how to load scenes with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-load-scene-from-blob/LoadSceneFromBlob.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-load-scene-from-blob/LoadSceneFromBlob.kt) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by passing a binary string that describes a scene to the `engine.scene.loadFromString()` function. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. In this example, we fetch a scene from a remote url and load it as `sceneBlob`. ``` val sceneUrl = URL("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene") val sceneBlob = withContext(Dispatchers.IO) { val outputStream = ByteArrayOutputStream() sceneUrl.openStream().use { inputStream -> outputStream.use(inputStream::copyTo) } outputStream.toByteArray() } ``` To acquire a scene string from `sceneBlob`, we need to read its contents into a string. ``` val blobString = String(sceneBlob, Charsets.UTF_8) ``` We can then pass that string to the `suspend fun load(scene: String): DesignBlock` function. The editor will reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ``` val scene = engine.scene.load(scene = blobString) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` val text = engine.block.findByType(DesignBlockType.Text).first() engine.block.setDropShadowEnabled(text, enabled = true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). How to Use Shapes - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/using-shapes?language=kotlin&platform=android#setup # How to Use Shapes The `graphic` [design block](/docs/cesdk/engine/guides/blocks/) in CE.SDK allows you to modify and replace its shape. CreativeEditor SDK supports many different types of shapes, such as rectangles, lines, ellipses, polygons and custom vector paths. Similarly to blocks, each shape object has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-shapes/UsingShapes.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-shapes/UsingShapes.kt) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` val scene = engine.scene.create() val graphic = engine.block.create(DesignBlockType.Graphic) val imageFill = engine.block.createFill(FillType.Image) engine.block.setString( block = imageFill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_1.jpg", ) engine.block.setFill(graphic, fill = imageFill) engine.block.setWidth(graphic, value = 100F) engine.block.setHeight(graphic, value = 100F) engine.block.appendChild(parent = scene, child = graphic) engine.scene.zoomToBlock( graphic, paddingLeft = 40F, paddingTop = 40F, paddingRight = 40F, paddingBottom = 40F, ) ``` ## Accessing Shapes In order to query whether a block supports shapes, you should call the `fun supportsShape(block: DesignBlock): Boolean` API. Currently, only the `graphic` design block supports shape objects. ``` engine.block.supportsShape(graphic) // Returns true val text = engine.block.create(DesignBlockType.Text) engine.block.supportsShape(text) // Returns false ``` ## Creating Shapes `graphic` blocks don't have any shape after you create them, which leaves them invisible by default. In order to make them visible, we need to assign both a shape and a fill to the `graphic` block. You can find more information on fills [here](/docs/cesdk/engine/guides/using-fills/). In this example we have created and attached an image fill. In order to create a new shape, we must call the `fun createShape(type: ShapeType): DesignBlock` API. We currently support the following shape types: * `ShapeType.Rect` * `ShapeType.Line` * `ShapeType.Ellipse` * `ShapeType.Polygon` * `ShapeType.Star` * `ShapeType.VectorPath` ``` val rectShape = engine.block.createShape(ShapeType.Rect) ``` In order to assign this shape to the `graphic` block, call the `fun setShape(block: DesignBlock, shape: DesignBlock)` API. ``` engine.block.setShape(graphic, shape = rectShape) ``` To query the shape of a design block, call the `fun getShape(block: DesignBlock): DesignBlock` API. You can now pass the returned result into other APIs in order to query more information about the shape, e.g. its type via the `fun getType(block: DesignBlock): String` API. ``` val shape = engine.block.getShape(graphic) val shapeType = engine.block.getType(shape) ``` When replacing the shape of a design block, remember to destroy the previous shape object if you don't intend to use it any further. Shape objects that are not attached to a design block will never be automatically destroyed. Destroying a design block will also destroy its attached shape block. ``` val starShape = engine.block.createShape(ShapeType.Star) engine.block.destroy(engine.block.getShape(graphic)) engine.block.setShape(graphic, shape = starShape) // The following line would also destroy the currently attached starShape // engine.block.destroy(graphic) ``` ## Shape Properties Just like design blocks, shapes with different types have different properties that you can query and modify via the API. Use `fun findAllProperties(block: DesignBlock): List` in order to get a list of all properties of a given shape. For the star shape in this example, the call would return `["name", "shape/star/innerDiameter", "shape/star/points", "type", "uuid"]`. Please refer to the [API docs](/docs/cesdk/engine/api/block-shape-types/) for a complete list of all available properties for each type of shape. ``` val allShapeProperties = engine.block.findAllProperties(starShape) ``` Once we know the property keys of a shape, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `fun setInt(block: DesignBlock, property: String, value: Int)` in order to change the number of points of the star to six. ``` engine.block.setInt(starShape, property = "shape/star/points", value = 6) ``` Layout - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-layout?language=kotlin&platform=android#setup # Layout In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes layout through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Layout of Blocks #### Note on layout and frame size The frame size is determined during the layout phase of the render process inside the engine. This means that calling `getFrameSize()` immediately after modifying the scene might return an inaccurate result. The CreativeEngine supports three different modes for positioning blocks. These can be set for each block and both coordinates independently: * `Absolute`: the position value is interpreted in the scene's current design unit. * `Percent`: the position value is interpreted as percentage of the block's parent's size, where 1F means 100%. * `Auto` : the position is automatically determined. Likewise there are also three different modes for controlling a block's size. Again both dimensions can be set independently: * `Absolute`: the size value is interpreted in the scene's current design unit. * `Percent`: the size value is interpreted as percentage of the block's parent's size, where 1F means 100%. * `Auto` : the block's size is automatically determined by the size of the block's content. ### Positioning ``` fun getPositionX(block: DesignBlock): Float ``` Query a block's x position. * `block`: the block to query. * Returns the value of the x position. ``` val x = engine.block.getPositionX(block) ``` ``` fun getPositionY(block: DesignBlock): Float ``` Query a block's y position. * `block`: the block to query. * Returns the value of the y position. ``` val y = engine.block.getPositionY(block) ``` ``` fun getPositionXMode(block: DesignBlock): PositionMode ``` Query a block's mode for its x position. * `block`: the block to query. * Returns the current mode for the x position. ``` val xMode = engine.block.getPositionXMode(block) ``` ``` fun getPositionYMode(block: DesignBlock): PositionMode ``` Query a block's mode for its y position. * `block`: the block to query. * Returns the current mode for the y position. ``` val yMode = engine.block.getPositionYMode(block) ``` ``` fun setPositionX( block: DesignBlock, value: Float, ) ``` Update a block's x position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: "layer/move" * `block`: the block to update. * `value`: the value of the x position. ``` engine.block.setPositionX(block, value = 0.25F) ``` ``` fun setPositionY( block: DesignBlock, value: Float, ) ``` Update a block's y position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: "layer/move" * `block`: the block to update. * `value`: the value of the y position. ``` engine.block.setPositionY(block, value = 0.25) ``` ``` fun setPositionXMode( block: DesignBlock, mode: PositionMode, ) ``` Set a block's mode for its x position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: "layer/move" * `block`: the block to update. * `mode`: the x position mode. ``` engine.block.setPositionXMode(block, mode = PositionMode.PERCENT) ``` ``` fun setPositionYMode( block: DesignBlock, mode: PositionMode, ) ``` Set a block's mode for its y position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: "layer/move" * `block`: the block to update. * `mode`: the y position mode. ``` engine.block.setPositionYMode(block, mode = PositionMode.PERCENT) ``` ### Size ``` fun getWidth(block: DesignBlock): Float ``` Query a block's width. * `block`: the block to query. * Returns the value of the block's width. ``` val width = engine.block.getWidth(block) ``` ``` fun getWidthMode(block: DesignBlock): SizeMode ``` Query a block's mode for its width. * `block`: the block to query. * Returns the current mode for the width. ``` val widthMode = engine.block.getWidthMode(block) ``` ``` fun getHeight(block: DesignBlock): Float ``` Query a block's height. * `block`: the block to query. * Returns the value of the block's height. ``` val height = engine.block.getHeight(block) ``` ``` fun getHeightMode(block: DesignBlock): SizeMode ``` Query a block's mode for its height. * `block`: the block to query. * Returns the current mode for the height. ``` val heightMode = engine.block.getHeightMode(block) ``` ``` fun setWidth( block: DesignBlock, value: Float, maintainCrop: Boolean = false, ) ``` Update a block's width and optionally maintain the crop. If the crop is maintained, the crop values will be automatically adjusted. The content fill mode `Cover` is only kept if the `features/transformEditsRetainCoverMode` setting is enabled, otherwise it will change to `Crop`. Required scope: "layer/resize" * `block`: the block to update. * `value`: the new width of the block. * `maintainCrop`: whether or not the crop values, if available, should be automatically adjusted. ``` engine.block.setWidth(block, value = 0.5F) engine.block.setWidth(block, value = 2.5F, maintainCrop = true) ``` ``` fun setWidthMode( block: DesignBlock, mode: SizeMode, ) ``` Set a block's mode for its width. Required scope: "layer/resize" * `block`: the block to update. * `mode`: the width mode. ``` engine.block.setWidthMode(block, mode = SizeMode.PERCENT) ``` ``` fun setHeight( block: DesignBlock, value: Float, maintainCrop: Boolean = false, ) ``` Update a block's height and optionally maintain the crop. If the crop is maintained, the crop values will be automatically adjusted. The content fill mode `Cover` is only kept if the `features/transformEditsRetainCoverMode` setting is enabled, otherwise it will change to `Crop`. Required scope: "layer/resize" * `block`: the block to update. * `value`: the new height of the block. * `maintainCrop`: whether or not the crop values, if available, should be automatically adjusted. ``` engine.block.setHeight(block, value = 0.5F) engine.block.setHeight(block, value = 2.5F, maintainCrop = true) ``` ``` fun setHeightMode( block: DesignBlock, mode: SizeMode, ) ``` Set a block's mode for its height. Required scope: "layer/resize" * `block`: the block to update. * `mode`: the height mode. ``` engine.block.setHeightMode(block, mode = SizeMode.PERCENT) ``` ### Rotation ``` fun getRotation(block: DesignBlock): Float ``` Query a block's rotation in radians. * `block`: the block to query. * Returns the block's rotation around its center in radians. ``` val rad = engine.block.getRotation(block) ``` ``` fun setRotation( block: DesignBlock, radians: Float, ) ``` Update a block's rotation. Required scope: "layer/rotate" * `block`: the block to update. * `radians`: the new rotation in radians. Rotation is applied around the block's center. ``` engine.block.setRotation(block, radians = PI.toFloat()) ``` ### Flipping ``` fun setFlipHorizontal( block: DesignBlock, flip: Boolean, ) ``` Update a block's horizontal flip. Required scope: "layer/flip" * `block`: the block to update. * `flip`: if the flip should be enabled. ``` engine.block.setFlipHorizontal(block, flip = true) ``` ``` fun isFlipHorizontal(block: DesignBlock): Boolean ``` Query a block's horizontal flip state. * `block`: the block to query. * Returns a boolean indicating for whether the block is flipped in the queried direction. ``` val isFlipHorizontal = engine.block.isFlipHorizontal(block) ``` ``` fun setFlipVertical( block: DesignBlock, flip: Boolean, ) ``` Update a block's vertical flip. Required scope: "layer/flip" * `block`: the block to update. * `flip`: if the flip should be enabled. ``` engine.block.setFlipVertical(block, flip = false) ``` ``` fun isFlipVertical(block: DesignBlock): Boolean ``` Query a block's vertical flip state. * `block`: the block to query. * Returns a boolean indicating for whether the block is flipped in the queried direction. ``` val isFlipVertical = engine.block.isFlipVertical(block) ``` ### Scaling ``` fun scale( block: DesignBlock, scale: Float, @FloatRange(from = 0.0, to = 1.0) anchorX: Float, @FloatRange(from = 0.0, to = 1.0) anchorY: Float, ) ``` Scales the block and all of its children proportionally around the specified relative anchor point. This updates the position, size and style properties (e.g. stroke width) of the block and its children. Required scope: "layer/resize" * `block`: the block that should be scaled. * `scale`: the scale factor to be applied to the current properties of the block. * `anchorX`: the relative position along the width of the block around which the scaling should occur. (0F = left edge, 0.5F = center, 1F = right edge) * `anchorY`: the relative position along the height of the block around which the scaling should occur. (0F = top edge, 0.5F = center, 1F = bottom edge) ``` engine.block.scale(block, scale = 2F, anchorX = 0.5F, anchorY = 0.5F) ``` ### Fill a Block's Parent ``` fun fillParent(block: DesignBlock) ``` Resize and position a block to entirely fill its parent block. Required scope: "layer/move" * "layer/resize" * `block`: The block that should fill its parent. ``` engine.block.fillParent(block) ``` ### Resize Blocks Content-aware ``` fun resizeContentAware( blocks: List, width: Float, height: Float, ) ``` Resize all blocks to the given size. The content of the blocks is automatically adjusted to fit the new dimensions. Required scope: "layer/resize" * `blocks`: The blocks to resize. * `width`: The new width of the blocks. * `height`: The new height of the blocks. ``` val pages = engine.scene.getPages() engine.block.resizeContentAware(pages, width = 100F, height = 100F) ``` ### Even Distribution ``` fun isDistributable(blocks: List): Boolean ``` Confirms that a given set of blocks can be distributed. * `blocks`: a non-empty array of block ids. * Returns whether the blocks can be distributed. ``` if (engine.block.isDistributable(listOf(member1, member2))) { ``` ``` fun distributeHorizontally(blocks: List) ``` Distribute multiple blocks vertically within their bounding box so that the space between them is even. Required scope: "layer/move" * `blocks`: a non-empty array of block ids that should be distributed. ``` engine.block.distributeHorizontally(listOf(member1, member2)) ``` ``` fun distributeVertically(blocks: List) ``` Distribute multiple blocks vertically within their bounding box so that the space between them is even. Required scope: "layer/move" * `blocks`: a non-empty array of block ids that should be distributed. ``` engine.block.distributeVertically(listOf(member1, member2)) ``` ### Alignment ``` fun isAlignable(blocks: List): Boolean ``` Confirms that a given set of blocks can be aligned. * `blocks`: a non-empty array of block ids. * Returns whether the blocks can be aligned. ``` if (engine.block.isAlignable(listOf(member1, member2))) { ``` ``` fun alignHorizontally( blocks: List, alignment: HorizontalBlockAlignment, ) ``` Align multiple blocks vertically within their bounding box or a single block to its parent. Required scope: "layer/move" * `blocks`: a non-empty array of block ids that should be aligned. * `alignment`: How they should be aligned: left, right, or center ``` engine.block.alignHorizontally(listOf(member1, member2), alignment = HorizontalBlockAlignment.LEFT) ``` ``` fun alignVertically( blocks: List, alignment: VerticalBlockAlignment, ) ``` Align multiple blocks vertically within their bounding box or a single block to its parent. Required scope: "layer/move" * `blocks`: a non-empty array of block ids that should be aligned. * `alignment`: How they should be aligned: top, bottom, or center ``` engine.block.alignVertically(listOf(member1, member2), alignment = VerticalBlockAlignment.TOP) ``` ### Computed Dimensions ``` fun getFrameX(block: DesignBlock): Float ``` Get a block's layout position on the x-axis. The layout position is only available after an internal update loop, which may not happen immediately. * `block`: the block to query. * Returns the layout position. ``` val frameX = engine.block.getFrameX(block) ``` ``` fun getFrameY(block: DesignBlock): Float ``` Get a block's layout position on the y-axis. The layout position is only available after an internal update loop, which may not happen immediately. * `block`: the block to query. * Returns the layout position. ``` val frameY = engine.block.getFrameY(block) ``` ``` fun getFrameWidth(block: DesignBlock): Float ``` Get a block's layout width. The layout width is only available after an internal update loop, which may not happen immediately. * `block`: the block to query. * Returns the layout width. ``` val frameWidth = engine.block.getFrameWidth(block) ``` ``` fun getFrameHeight(block: DesignBlock): Float ``` Get a block's layout height. The layout height is only available after an internal update loop, which may not happen immediately. * `block`: the block to query. * Returns the layout height. ``` val frameHeight = engine.block.getFrameHeight(block) ``` ``` fun getGlobalBoundingBoxX(block: DesignBlock): Float ``` Get the x position of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `block`: the block to query. * Returns the x coordinate of the position of the axis-aligned bounding box. ``` val globalX = engine.block.getGlobalBoundingBoxX(block) ``` ``` fun getGlobalBoundingBoxY(block: DesignBlock): Float ``` Get the y position of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `block`: the block to query. * Returns the y coordinate of the position of the axis-aligned bounding box. ``` val globalY = engine.block.getGlobalBoundingBoxY(block) ``` ``` fun getGlobalBoundingBoxWidth(block: DesignBlock): Float ``` Get the width of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `block`: the block to query. * Returns the width of the axis-aligned bounding box. ``` val globalWidth = engine.block.getGlobalBoundingBoxWidth(block) ``` ``` fun getGlobalBoundingBoxHeight(block: DesignBlock): Float ``` Get the height of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `block`: the block to query. * Returns the height of the axis-aligned bounding box. ``` val globalHeight = engine.block.getGlobalBoundingBoxHeight(block) ``` ``` fun getScreenSpaceBoundingBoxRect(blocks: List): RectF ``` Get the position and size of the axis-aligned bounding box for the given blocks in screen space. * `blocks`: the blocks whose bounding box should be calculated. * Returns the position and size of the bounding box as RectF. ``` val screenSpaceRect = engine.block.getScreenSpaceBoundingBoxRect(listOf(block)) ``` ### Layers ``` fun setAlwaysOnTop( block: DesignBlock, enabled: Boolean, ) ``` Update the block's always-on-top property. If true, this blocks's global sorting order is automatically adjusted to be higher than all other siblings without this property. If more than one block is set to be always-on-top, the child order decides which is on top. * `block`: the block to update. * `enabled`: whether the block shall be always-on-top. ``` engine.block.setAlwaysOnTop(block, false) ``` ``` fun isAlwaysOnTop(block: DesignBlock): Boolean ``` Query a block's always-on-top property. * `block`: the block to query. * Returns true if the block is set to be always-on-top, false otherwise. ``` val isAlwaysOnTop = engine.block.isAlwaysOnTop(block) ``` ``` fun setAlwaysOnBottom( block: DesignBlock, enabled: Boolean, ) ``` Update the block's always-on-bottom property. If true, this blocks's global sorting order is automatically adjusted to be lower than all other siblings without this property. If more than one block is set to be always-on-bottom, the child order decides which is on the bottom. * `block`: the block to update. * `enabled`: whether the block shall be always-on-bottom. ``` engine.block.setAlwaysOnBottom(block, false) ``` ``` fun isAlwaysOnBottom(block: DesignBlock): Boolean ``` Query a block's always-on-bottom property. * `block`: the block to query. * Returns true if the block is set to be always-on-bottom, false otherwise. ``` val isAlwaysOnBottom = engine.block.isAlwaysOnBottom(block) ``` ``` fun bringToFront(block: DesignBlock) ``` Updates the sorting order of this block and all of its manually created siblings so that the given block has the highest sorting order. If the block is parented to a track, it is first moved up in the hierarchy. * `block`: the block to be given the highest sorting order among its siblings. ``` engine.block.bringToFront(block) ``` ``` fun sendToBack(block: DesignBlock) ``` Updates the sorting order of this block and all of its manually created siblings so that the given block has the lowest sorting order. If the block is parented to a track, it is first moved up in the hierarchy. * `block`: the block to be given the lowest sorting order among its siblings. ``` engine.block.sendToBack(block) ``` ``` fun bringForward(block: DesignBlock) ``` Updates the sorting order of this block and all of its superjacent siblings so that the given block has a higher sorting order than the next superjacent sibling. If the block is parented to a track, it is first moved up in the hierarchy. * `block`: the block to be given a higher sorting than the next superjacent sibling. ``` engine.block.bringForward(block) ``` ``` fun sendBackward(block: DesignBlock) ``` Updates the sorting order of this block and all of its manually created and subjacent siblings so that the given block will have a lower sorting order than the next subjacent sibling. If the block is parented to a track, it is first moved up in the hierarchy. * `block`: the block to be given a lower sorting order than the next subjacent sibling. ``` engine.block.sendBackward(block) ``` ### Transform Locking You can lock the transform of a block to prevent changes to any of its transformations. That is the block's position, rotation, scale, and sizing. ``` fun isTransformLocked(block: DesignBlock): Boolean ``` Query a block's transform locked state. If `true`, the block's transform can't be changed. * `block`: the block to query. * Returns true if block is locked, false otherwise. ``` val isTransformLocked = engine.block.isTransformLocked(block) ``` ``` fun setTransformLocked( block: DesignBlock, locked: Boolean, ) ``` Update a block's transform locked state. * `block`: the block to update. * `locked`: whether the block's transform should be locked. ``` engine.block.setTransformLocked(block, locked = true) ``` Change Settings - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/editor-change-settings?language=kotlin&platform=android#setup # Change Settings In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control with the `editor` API. A list of all available settings can be found on the [Settings](/docs/cesdk/engine/api/editor-settings/) page. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Exploration ``` fun findAllSettings(): List ``` Returns a list of all the settings available. * Returns the list of all settings. ``` engine.editor.findAllSettings() ``` ``` fun getSettingType(keypath: String): PropertyType ``` Get the type of a setting. * `keypath`: the settings enum keypath, e.g. `doubleClickSelectionMode`. * Returns the type of the setting. ``` engine.editor.getSettingType("doubleClickSelectionMode") ``` ## Functions ``` fun onSettingsChanged(): Flow ``` Subscribe to changes to the editor settings. * Returns flow of editor settings change events. ``` engine.editor.onSettingsChanged() .onEach { println("Editor settings have changed") } .launchIn(CoroutineScope(Dispatchers.Main)) ``` ``` fun onRoleChanged(): Flow ``` Subscribe to changes to the editor role. * Returns flow of role change events. ``` engine.editor.onRoleChanged() .onEach { role -> println("Role changed to $role") } .launchIn(CoroutineScope(Dispatchers.Main)) ``` ``` fun setSettingBoolean( keypath: String, value: Boolean, ) ``` Set a boolean setting. * `keypath`: the settings keypath, e.g. `doubleClickToCropEnabled`. * `value`: the value to set. ``` engine.editor.setSettingBoolean("doubleClickToCropEnabled", value = true) ``` ``` fun getSettingBoolean(keypath: String): Boolean ``` Get a boolean setting. * `keypath`: the settings keypath, e.g. `doubleClickToCropEnabled`. * Returns the current value. ``` engine.editor.getSettingBoolean("doubleClickToCropEnabled") ``` ``` fun setSettingInt( keypath: String, value: Int, ) ``` Set an integer setting. * `keypath`: the settings keypath. * `value`: the value to set. ``` try engine.editor.setSettingInt("integerSetting", value: 0) ``` ``` fun getSettingInt(keypath: String): Int ``` Get an integer setting. * `keypath`: the settings keypath. * Returns the current value. ``` try engine.editor.getSettingInt("integerSetting") ``` ``` fun setSettingFloat( keypath: String, value: Float, ) ``` Set a float setting. * `keypath`: the settings keypath, e.g. `positionSnappingThreshold`. * `value`: the value to set. ``` engine.editor.setSettingFloat("positionSnappingThreshold", value = 2.0F) ``` ``` fun getSettingFloat(keypath: String): Float ``` Get a float setting. * `keypath`: the settings keypath, e.g. `positionSnappingThreshold`. * Returns the current value. ``` engine.editor.getSettingFloat("positionSnappingThreshold") ``` ``` fun setSettingString( keypath: String, value: String, ) ``` Set a string setting. * `keypath`: the settings keypath, e.g. `license`. * `value`: the value to set. ``` engine.editor.setSettingString("license", value = "invalid") ``` ``` fun getSettingString(keypath: String): String ``` Get a string setting. * `keypath`: the settings keypath, e.g. `license`. * Returns the current value. ``` engine.editor.getSettingString("license") ``` ``` fun setSettingColor( keypath: String, value: RGBAColor, ) ``` Set a color setting. * `keypath`: the settings keypath, e.g. `highlightColor`. * `value`: the value to set. ``` engine.editor.setSettingColor("highlightColor", Color.fromRGBA(r = 1F, g = 0F, b = 1F, a = 1F)) // Pink ``` ``` fun getSettingColor(keypath: String): RGBAColor ``` Get a color setting. * `keypath`: the settings keypath, e.g. `highlightColor`. * Returns the current value. ``` engine.editor.getSettingColor("highlightColor") ``` ``` fun setSettingEnum( keypath: String, value: String, ) ``` Set an enum setting. * `keypath`: the settings keypath, e.g. `doubleClickSelectionMode`. * `value`: the value to set. ``` engine.editor.setSettingEnum("doubleClickSelectionMode", value = "Direct") ``` ``` fun getSettingEnum(keypath: String): String ``` Get an enum setting. * `keypath`: the settings keypath, e.g. `doubleClickSelectionMode`. * Returns the current value. ``` engine.editor.getSettingEnum("doubleClickSelectionMode") ``` ``` fun getSettingEnumOptions(keypath: String): List ``` Get all the available options of an enum setting. * `keypath`: the settings enum keypath, e.g. `doubleClickSelectionMode`. * Returns the list of enum options. ``` engine.editor.getSettingEnumOptions("doubleClickSelectionMode") ``` ``` fun getRole(): String ``` Get the current role of the user ``` engine.editor.getRole() ``` ``` fun setRole(role: String) ``` Set the role of the user and apply role-dependent defaults for scopes and settings ``` engine.editor.setRole("Adopter") ``` Configure Dock - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/dock/?platform=android&language=kotlin # Configure Dock In this example, we will show you how to make dock configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-dock). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-dock) ## Dock Architecture ![](/docs/cesdk/de62d411bb02859f263daa3fb25c7c4e/dock-android.png) `Dock` is a list of items placed horizontally at the bottom of the editor. It has type `Dock : EditorComponent` and every item in the dock has type `Dock.Item : EditorComponent`. `Dock.Item` is an abstract class that currently has two implementations: `Dock.Button` and `Dock.Custom`. `Dock.Button` is an editor component that has an icon and a text positioned in a column, while `Dock.Custom` is a fully custom editor component that allows drawing arbitrary content. Prefer using `Dock.Custom` for rendering custom content in the dock over inheriting from `Dock.Item`. ## Dock Configuration `Dock` is part of the `EditorConfiguration`, therefore, in order to configure the dock we need to configure the `EditorConfiguration`. Below you can find the list of available configurations of the dock. To demonstrate the default values, all parameters are assigned to their default values. Consider using `Dock.rememberFor{solution_name}` helper functions when providing a dock for a specific [solution](/docs/cesdk/mobile-editor/solutions/). ``` val editorConfiguration = EditorConfiguration.rememberForDesign( dock = { Dock.remember( scope = LocalEditorScope.current.run { remember(this) { Dock.Scope(parentScope = this) } }, listBuilder = Dock.ListBuilder.remember { }, horizontalArrangement = { Arrangement.SpaceEvenly }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, decoration = { // Also available via Dock.defaultDecoration Box( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surface1.copy(alpha = 0.95f)) .padding(vertical = 10.dp), ) { it() } }, // default value is { it() } itemDecoration = { Box(modifier = Modifier.padding(2.dp)) { it() } }, ) }, ) ``` * `scope` - the scope of this component. Every new value will trigger recomposition of all the lambda parameters below. If you need to access `EditorScope` to construct the scope, use `LocalEditorScope`. Consider using Compose `State` objects in the lambda parameters below for granular recompositions over updating the scope, since scope change triggers full recomposition of the dock. Prefer updating individual `Dock.Item`s over updating the whole dock. Ideally, scope should be updated when the parent scope (scope of the parent component) is updated and when you want to observe changes from the `Engine`. By default the scope is updated only when the parent scope (accessed via `LocalEditorScope`) is updated. ``` scope = LocalEditorScope.current.run { remember(this) { Dock.Scope(parentScope = this) } }, ``` * `listBuilder` - a builder that registers the list of `Dock.Item`s that should be part of the dock. Note that registering does not mean displaying. The items will be displayed if `Dock.Item.visible` is true for them. By default listBuilder does not add anything to the dock. For configuration details, see [ListBuilder Configuration](/docs/cesdk/mobile-editor/configuration/dock/#docklistbuilder-configuration) section. ``` listBuilder = Dock.ListBuilder.remember { }, ``` * `horizontalArrangement` - the horizontal arrangement that should be used to render the items in the dock horizontally. Default value is `Arrangement.SpaceEvenly`. ``` horizontalArrangement = { Arrangement.SpaceEvenly }, ``` * `visible` - whether the dock should be visible. Default value is always true. ``` visible = { true }, ``` * `enterTransition` - transition of the dock when it enters the parent composable. Default value is always no enter transition. ``` enterTransition = { EnterTransition.None }, ``` * `exitTransition` - transition of the dock when it exits the parent composable. Default value is always no exit transition. ``` exitTransition = { ExitTransition.None }, ``` * `decoration` - decoration of the dock. Useful when you want to add custom background, foreground, shadow, paddings etc. By default decoration adds background color and applies paddings to the dock. ``` decoration = { // Also available via Dock.defaultDecoration Box( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surface1.copy(alpha = 0.95f)) .padding(vertical = 10.dp), ) { it() } }, ``` * `itemDecoration` - decoration of the items in the dock. Useful when you want to add custom background, foreground, shadow, paddings etc to the items. Prefer using this decoration when you want to apply the same decoration to all the items, otherwise set decoration to individual items. Default value is always no decoration. ``` // default value is { it() } itemDecoration = { Box(modifier = Modifier.padding(2.dp)) { it() } }, ``` ## Dock.ListBuilder Configuration There are two main ways to create an instance of `Dock.ListBuilder`. First way is to call `modify` on an existing builder, and the second way is to create the builder from scratch. Currently, there are three available builders: ``` @Composable fun Dock.ListBuilder.rememberForDesign(): ListBuilder> { return Dock.ListBuilder.remember { add { Dock.Button.rememberElementsLibrary() } add { Dock.Button.rememberSystemGallery() } add { Dock.Button.rememberSystemCamera() } add { Dock.Button.rememberImagesLibrary() } add { Dock.Button.rememberTextLibrary() } add { Dock.Button.rememberShapesLibrary() } add { Dock.Button.rememberStickersLibrary() } } } @Composable fun Dock.ListBuilder.rememberForPhoto(): ListBuilder> { return Dock.ListBuilder.remember { add { Dock.Button.rememberAdjustments() } add { Dock.Button.rememberFilter() } add { Dock.Button.rememberEffect() } add { Dock.Button.rememberBlur() } add { Dock.Button.rememberCrop() } add { Dock.Button.rememberTextLibrary() } add { Dock.Button.rememberShapesLibrary() } add { Dock.Button.rememberStickersLibrary() } } } @Composable fun Dock.ListBuilder.rememberForVideo(): ListBuilder> { return Dock.ListBuilder.remember { add { Dock.Button.rememberSystemGallery() } add { /* Make sure to add the gradle dependency of our camera library if you want to use the [rememberImglyCamera] button: implementation "ly.img:camera:". If the dependency is missing, then [rememberSystemCamera] is used. */ val isImglyCameraAvailable = androidx.compose.runtime.remember { runCatching { CaptureVideo() }.isSuccess } if (isImglyCameraAvailable) { Dock.Button.rememberImglyCamera() } else { Dock.Button.rememberSystemCamera() } } add { Dock.Button.rememberOverlaysLibrary() } add { Dock.Button.rememberTextLibrary() } add { Dock.Button.rememberStickersLibrary() } add { Dock.Button.rememberAudiosLibrary() } add { Dock.Button.rememberReorder() } } } ``` * `Dock.ListBuilder.rememberForDesign()` that is recommended to be used in [Design Editor](/docs/cesdk/mobile-editor/solutions/design-editor/). ``` @Composable fun Dock.ListBuilder.rememberForDesign(): ListBuilder> { return Dock.ListBuilder.remember { add { Dock.Button.rememberElementsLibrary() } add { Dock.Button.rememberSystemGallery() } add { Dock.Button.rememberSystemCamera() } add { Dock.Button.rememberImagesLibrary() } add { Dock.Button.rememberTextLibrary() } add { Dock.Button.rememberShapesLibrary() } add { Dock.Button.rememberStickersLibrary() } } } ``` * `Dock.ListBuilder.rememberForPhoto()` that is recommended to be used in [Photo Editor](/docs/cesdk/mobile-editor/solutions/photo-editor/). ``` @Composable fun Dock.ListBuilder.rememberForPhoto(): ListBuilder> { return Dock.ListBuilder.remember { add { Dock.Button.rememberAdjustments() } add { Dock.Button.rememberFilter() } add { Dock.Button.rememberEffect() } add { Dock.Button.rememberBlur() } add { Dock.Button.rememberCrop() } add { Dock.Button.rememberTextLibrary() } add { Dock.Button.rememberShapesLibrary() } add { Dock.Button.rememberStickersLibrary() } } } ``` * `Dock.ListBuilder.rememberForVideo()` that is recommended to be used in [Video Editor](/docs/cesdk/mobile-editor/solutions/video-editor/). Note that `Dock.Button.rememberImglyCamera()` can be used only if `ly.img:camera:` dependency is added in your`build.gradle` file. For more information, check the camera [documentation](/docs/cesdk/mobile-camera/quickstart/). ``` @Composable fun Dock.ListBuilder.rememberForVideo(): ListBuilder> { return Dock.ListBuilder.remember { add { Dock.Button.rememberSystemGallery() } add { /* Make sure to add the gradle dependency of our camera library if you want to use the [rememberImglyCamera] button: implementation "ly.img:camera:". If the dependency is missing, then [rememberSystemCamera] is used. */ val isImglyCameraAvailable = androidx.compose.runtime.remember { runCatching { CaptureVideo() }.isSuccess } if (isImglyCameraAvailable) { Dock.Button.rememberImglyCamera() } else { Dock.Button.rememberSystemCamera() } } add { Dock.Button.rememberOverlaysLibrary() } add { Dock.Button.rememberTextLibrary() } add { Dock.Button.rememberStickersLibrary() } add { Dock.Button.rememberAudiosLibrary() } add { Dock.Button.rememberReorder() } } } ``` ### Modifying an Existing Builder In this example, we will modify `Dock.ListBuilder.rememberForDesign`. Modifying builders can be used, when you do not want to touch the default general order of the items in the builder, but rather add additional items and replace/hide some of the default items. To achieve that, there are multiple available functions in the scope of `modify` lambda: ``` listBuilder = Dock.ListBuilder.rememberForDesign().modify { addFirst { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.first"), vectorIcon = null, text = { "First Button" }, onClick = {}, ) } addLast { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.last"), vectorIcon = null, text = { "Last Button" }, onClick = {}, ) } addAfter(id = Dock.Button.Id.systemGallery) { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.afterSystemGallery"), vectorIcon = null, text = { "After System Gallery" }, onClick = {}, ) } addBefore(id = Dock.Button.Id.systemCamera) { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.beforeSystemCamera"), vectorIcon = null, text = { "Before System Camera" }, onClick = {}, ) } replace(id = Dock.Button.Id.textLibrary) { // Note that it can be replaced with a component that has a different id. Dock.Button.rememberElementsLibrary() } remove(id = Dock.Button.Id.shapesLibrary) }, ``` * `addFirst` - prepends a new `Dock.Item` in the list. ``` addFirst { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.first"), vectorIcon = null, text = { "First Button" }, onClick = {}, ) } ``` * `addLast` - appends a new `Dock.Item` in the list. ``` addLast { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.last"), vectorIcon = null, text = { "Last Button" }, onClick = {}, ) } ``` * `addAfter` - adds a new `Dock.Item` right after the item with provided id. An exception will be thrown if no item exists with provided id in the default builder. ``` addAfter(id = Dock.Button.Id.systemGallery) { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.afterSystemGallery"), vectorIcon = null, text = { "After System Gallery" }, onClick = {}, ) } ``` * `addBefore` - adds a new `Dock.Item` right before the item with provided id. An exception will be thrown if no item exists with provided id in the default builder. ``` addBefore(id = Dock.Button.Id.systemCamera) { Dock.Button.remember( id = EditorComponentId("my.package.dock.button.beforeSystemCamera"), vectorIcon = null, text = { "Before System Camera" }, onClick = {}, ) } ``` * `replace` - replaces the `Dock.Item` with provided id. An exception will be thrown if no item exists with provided id in the default builder. Also note that the new item does not need to have the same id. ``` replace(id = Dock.Button.Id.textLibrary) { // Note that it can be replaced with a component that has a different id. Dock.Button.rememberElementsLibrary() } ``` * `remove` - removes the `Dock.Item` with provided id. An exception will be thrown if no item exists with provided id in the default builder. ``` remove(id = Dock.Button.Id.shapesLibrary) ``` #### Warning Note that the order of items may change between editor versions, therefore ListBuilder.modify must be used with care. Consider creating a new builder if you want to have strict ordering between different editor versions. ### Creating a New Builder In this example, we will create a builder from scratch that will be used in the `Dock` of `DesignEditor` solution. Creating a new builder is recommended, when you want to touch the default order of available builders, as well as when available builders do not contain the items that you want. This example mimics reordering the default order of items in `Dock.ListBuilder.rememberForDesign` builder. In addition, some items are removed and a new custom item is prepended. ``` listBuilder = Dock.ListBuilder.remember { add { val buttonScope = LocalEditorScope.current.run { remember(this) { Dock.ButtonScope(parentScope = this) } } Dock.Button.remember( id = EditorComponentId("my.package.dock.button.custom"), vectorIcon = null, text = { "Custom Button" }, onClick = {}, scope = buttonScope, ) } add { Dock.Button.rememberSystemGallery() } add { Dock.Button.rememberSystemCamera() } add { Dock.Button.rememberElementsLibrary() } add { Dock.Button.rememberStickersLibrary() } add { Dock.Button.rememberImagesLibrary() } add { Dock.Button.rememberTextLibrary() } }, ``` ## Dock.Item Configuration As mentioned in the [Dock Architecture](/docs/cesdk/mobile-editor/configuration/dock/#dock-architecture) section, `Dock.Item` is an `EditorComponent` and it has two subtypes: `Dock.Button` and `Dock.Custom`. ### Dock.Button Configuration In order to create a dock button, use `Dock.Button.remember` composable function. Below you can find the list of available configurations when creating a `Dock.Button`. To demonstrate the default values, all parameters are assigned to their default values whenever possible. ``` @Composable fun rememberDockButton(): Dock.Button { return Dock.Button.remember( id = EditorComponentId("my.package.dock.button.newButton"), scope = LocalEditorScope.current.run { remember(this) { Dock.ButtonScope(parentScope = this) } }, onClick = { editorContext.eventHandler.send(EditorEvent.Sheet.Open(SheetType.Volume())) }, // default value is null icon = { Icon( imageVector = IconPack.Music, contentDescription = null, ) }, // default value is null text = { Text( text = "Hello World", ) }, enabled = { true }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, // default value is { it() } decoration = { Surface(color = MaterialTheme.colorScheme.background) { it() } }, ) } ``` * `id` - the id of the button. Note that it is highly recommended that every unique `EditorComponent` has a unique id. Parameter does not have a default value. ``` id = EditorComponentId("my.package.dock.button.newButton"), ``` * `scope` - the scope of this component. Every new value will trigger recomposition of all the lambda parameters below. If you need to access `EditorScope` to construct the scope, use `LocalEditorScope`. Consider using Compose `State` objects in the lambda parameters below for granular recompositions over updating the scope, since scope change triggers full recomposition of the button. Ideally, scope should be updated when the parent scope (scope of the parent component `Dock` - `Dock.Scope`) is updated and when you want to observe changes from the `Engine`. By default the scope is updated only when the parent component scope (`Dock.scope`, accessed via `LocalEditorScope`) is updated. ``` scope = LocalEditorScope.current.run { remember(this) { Dock.ButtonScope(parentScope = this) } }, ``` * `onClick` - the callback that is invoked when the button is clicked. Parameter does not have a default value. ``` onClick = { editorContext.eventHandler.send(EditorEvent.Sheet.Open(SheetType.Volume())) }, ``` * `icon` - the icon content of the button. If null, it will not be rendered. Default value is null. ``` // default value is null icon = { Icon( imageVector = IconPack.Music, contentDescription = null, ) }, ``` * `text` - the text content of the button. If null, it will not be rendered. Default value is null. ``` // default value is null text = { Text( text = "Hello World", ) }, ``` * `enabled` - whether the button is enabled. Default value is always true. ``` enabled = { true }, ``` * `visible` - whether the button should be visible. Default value is always true. ``` visible = { true }, ``` * `enterTransition` - transition of the button when it enters the parent composable. Default value is always no enter transition. ``` enterTransition = { EnterTransition.None }, ``` * `exitTransition` - transition of the button when it exits the parent composable. Default value is always no exit transition. ``` exitTransition = { ExitTransition.None }, ``` * `decoration` - decoration of the button. Useful when you want to add custom background, foreground, shadow, paddings etc. Default value is always no decoration. ``` // default value is { it() } decoration = { Surface(color = MaterialTheme.colorScheme.background) { it() } }, ``` Other than the main `Dock.Button.remember` function, there is one more convenience overload that has two differences: 1. `icon` is replaced with `vectorIcon` lambda, that returns `VectorIcon` instead of drawing the icon content. 2. `text` is replaced with `text` lambda, that returns `String` instead of drawing the text content. ``` @Composable fun rememberDockButtonSimpleOverload(): Dock.Button { return Dock.Button.remember( id = EditorComponentId("my.package.dock.button.newButton"), scope = LocalEditorScope.current.run { remember(this) { Dock.ButtonScope(parentScope = this) } }, onClick = { editorContext.eventHandler.send(ShowLoading) }, vectorIcon = { IconPack.Music }, // default value is null text = { "Hello World" }, // default value is null tint = null, enabled = { true }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, decoration = { Surface(color = MaterialTheme.colorScheme.background) { it() } }, ) } ``` ### Dock.Custom Configuration In order to create a custom dock item, use `Dock.Custom.remember` composable function. Below you can find the list of available configurations when creating a `Dock.Custom` item. To demonstrate the default values, all parameters are assigned to their default values whenever possible. ``` @Composable fun rememberCustomItem(): Dock.Item { return Dock.Custom.remember( id = EditorComponentId("my.package.dock.button.newCustomItem"), scope = LocalEditorScope.current.run { remember(this) { Dock.ItemScope(parentScope = this) } }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, ) { Box( modifier = Modifier .fillMaxHeight() .clickable { Toast.makeText(editorContext.activity, "Hello World Clicked!", Toast.LENGTH_SHORT).show() }, ) { Text( modifier = Modifier.align(Alignment.Center), text = "Hello World", ) } } } ``` * `id` - the id of the custom item. Note that it is highly recommended that every unique `EditorComponent` has a unique id. Parameter does not have a default value. ``` id = EditorComponentId("my.package.dock.button.newCustomItem"), ``` * `scope` - the scope of this component. Every new value will trigger recomposition of all the lambda parameters below. If you need to access `EditorScope` to construct the scope, use `LocalEditorScope`. Consider using Compose `State` objects in the lambda parameters below for granular recompositions over updating the scope, since scope change triggers full recomposition of the custom item. Ideally, scope should be updated when the parent scope (scope of the parent component `Dock` - `Dock.Scope`) is updated and when you want to observe changes from the `Engine`. Parameter does not have a default value. ``` scope = LocalEditorScope.current.run { remember(this) { Dock.ItemScope(parentScope = this) } }, ``` * `visible` - whether the custom item should be visible. Default value is always true. ``` visible = { true }, ``` * `enterTransition` - transition of the custom item when it enters the parent composable. Default value is always no enter transition. ``` enterTransition = { EnterTransition.None }, ``` * `exitTransition` - transition of the custom item when it exits the parent composable. Default value is always no exit transition. ``` exitTransition = { ExitTransition.None }, ``` * `content` - the content of the custom item. You are responsible for drawing it, handling clicks etc. Parameter does not have a default value. ``` Box( modifier = Modifier .fillMaxHeight() .clickable { Toast.makeText(editorContext.activity, "Hello World Clicked!", Toast.LENGTH_SHORT).show() }, ) { Text( modifier = Modifier.align(Alignment.Center), text = "Hello World", ) } ``` Configure UI Events - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/ui-events/?platform=android&language=kotlin # Configure UI Events In this example, we will show you how to make UI events configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-ui-events/UiEventsEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-ui-events/UiEventsEditorSolution.kt) ## Configuration When working with the [callbacks](/docs/cesdk/mobile-editor/configuration/callbacks/), you may want to make UI updates before, during, or after the callback execution. This is when UI events come to help. All the callbacks receive an extra parameter `EditorEventHandler`, which can be used to send editor events. By default, there are existing ui events, which can be found in `Events.kt` file (i.e. `ShowLoading`, `HideLoading` etc). ``` onCreate = { EditorDefaults.onCreate( engine = editorContext.engine, sceneUri = EngineConfiguration.defaultDesignSceneUri, eventHandler = editorContext.eventHandler, ) editorContext.eventHandler.send(OnCreateCustomEvent) }, ``` You may want to declare your own custom event. Simply inherit from class `EditorEvent`. ``` data object OnCreateCustomEvent : EditorEvent ``` After declaring the event, you can send the UI event using `send` function. Note that `EditorEventHandler` has another function called `sendCloseEditorEvent`, which can be used to forcefully close the mobile editor. ``` editorContext.eventHandler.send(OnCreateCustomEvent) ``` Once the event is sent, it can be captured in `EditorConfiguration.onEvent`. The lambda contains three parameters: `activity`, `state` and of course, the captured `event`. In this example, the editor `state` is of default type `EditorUiState` (initially provided in `initialState`), however, you can have your own state class that wraps the `EditorUiState` or does not contain `EditorUiState` at all. The only requirement is that it should be `Parcelable`. The lambda should return the updated state, which, if changed, will trigger [overlay](/docs/cesdk/mobile-editor/configuration/overlay/) composable to be recomposed and the overlaying ui components will be updated. ``` onEvent = { state, event -> when (event) { OnCreateCustomEvent -> { Toast.makeText(editorContext.activity, "Editor is created!", Toast.LENGTH_SHORT).show() state } ShowLoading -> { state.copy(showLoading = true) } else -> { // handle other default events EditorDefaults.onEvent(editorContext.activity, state, event) } } }, ``` To handle your brand new custom event, simply check the type of the event and handle it per your needs. ``` OnCreateCustomEvent -> { Toast.makeText(editorContext.activity, "Editor is created!", Toast.LENGTH_SHORT).show() state } ``` Besides, you can override the behavior of existing events too. Simply extend your `when` block and override the behavior. ``` ShowLoading -> { state.copy(showLoading = true) } ``` If you want to leave the behavior of remaining default events unchanged, simply return the result of `EditorDefaults.onEvent` in the `else` block. ``` else -> { // handle other default events EditorDefaults.onEvent(editorContext.activity, state, event) } ``` Load Scenes from a Remote URL - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/load-scene-from-url?language=kotlin&platform=android#warning # Load Scenes from a Remote URL In this example, we will show you how to load scenes from a remote URL with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-load-scene-from-remote/LoadSceneFromRemote.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-load-scene-from-remote/LoadSceneFromRemote.kt) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by fetching a scene file or an archive file from a URL. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. Determine a url that points to a scene binary string. Create an instance of `Uri` using the remote url. ``` val sceneUri = Uri.parse( "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene", ) ``` We can then pass the object `sceneUri` to the `suspend fun load(sceneUri: Uri): DesignBlock` function. The editor will reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ``` val scene = engine.scene.load(sceneUri = sceneUri) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` val text = engine.block.findByType(DesignBlockType.Text).first() engine.block.setDropShadowEnabled(text, enabled = true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Observe Editing State - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/editor-state?language=kotlin&platform=android#setup # Observe Editing State In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to set and query the editor state in the `editor` API, i.e., what type of content the user is currently able to edit. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## State The editor state consists of the current edit mode, which informs what type of content the user is currently able to edit. The edit mode can be set to either `Transform`, `Crop`, `Text`, or a custom user-defined one. You can also query the intended mouse cursor and the location of the text cursor while editing text. Instead of having to constantly query the state in a loop, you can also be notified when the state has changed to then act on these changes in a callback. ``` fun onStateChanged(): Flow ``` Subscribe to changes to the editor state. * Returns flow of editor state change events. ``` engine.editor.onStateChanged() .onEach { println("Editor history has changed") } .launchIn(CoroutineScope(Dispatchers.Main)) ``` ``` fun setEditMode(editMode: String) ``` Set the edit mode of the editor. An edit mode defines what type of content can currently be edited by the user. Note: The initial edit mode is "Transform". * `editMode`: "Transform", "Crop", "Text", "Playback", "Trim" or a custom value. ``` engine.editor.setEditMode("Crop") ``` ``` fun getEditMode(): String ``` Get the current edit mode of the editor. An edit mode defines what type of content can currently be edited by the user. * Returns "Transform", "Crop", "Text", "Playback", "Trim" or a custom value. ``` engine.editor.getEditMode() // "Crop" ``` ``` @UnstableEngineApi fun isInteractionHappening(): Boolean ``` If an user interaction is happening, e.g., a resize edit with a drag handle or a touch gesture. * Returns true if an interaction is happening. ``` engine.editor.isInteractionHappening() ``` ## Cursor ``` fun getTextCursorPositionInScreenSpaceX(): Float ``` Get the current text cursor's x position in screen space. * Returns the text cursor's x position in screen space. ``` engine.editor.getTextCursorPositionInScreenSpaceX() ``` ``` fun getTextCursorPositionInScreenSpaceY(): Float ``` Get the current text cursor's y position in screen space. * Returns the text cursor's y position in screen space. ``` engine.editor.getTextCursorPositionInScreenSpaceY() ``` Fills - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-fills?language=kotlin&platform=android#setup # Fills In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's fill through the `block` API. The fill defines the visual contents within a block's shape. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Fill To create a fill simply use `fun createFill(type: String): DesignBlock`. ``` val solidColor = engine.block.createFill(type = FillType.Color) ``` ``` fun createFill(fillType: FillType): DesignBlock ``` Create a new fill, fails if type is unknown. * `fillType`: the type of the fill object that shall be created. * Returns the created fill's handle. ``` val solidColor = engine.block.createFill(type = FillType.Color) ``` We currently support the following fill types: * `FillType.Color` * `FillType.LinearGradient` * `FillType.RadialGradient` * `FillType.ConicalGradient` * `FillType.Image` * `FillType.Video` * `FillType.PixelStream` ``` val solidColor = engine.block.createFill(type = FillType.Color) ``` ## Functions You can configure fills just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setColor( solidColor, property = "fill/color/value", value = Color.fromRGBA(r = 0.44F, g = 0.76F, b = 0.76F, a = 1F) ) ``` ``` fun getFill(block: DesignBlock): DesignBlock ``` Returns the block containing the fill properties of the given block. * `block`: the block whose fill block should be returned. * Returns the block that currently defines the given block's fill. ``` val previousFill = engine.block.getFill(block) ``` Remember to first destroy the previous fill if you don't need it any more. A single fill can also be connected to multiple design blocks. This way, modifying the properties of the fill will apply the changes to all connected design blocks at once. ``` fun setFill( block: DesignBlock, fill: DesignBlock, ) ``` Sets the block containing the fill properties of the given block. Note: The previous fill block is not destroyed automatically. Required scopes: "fill/change", "fill/changeType" * `block`: the block whose fill should be changed. * `fill`: the new fill. ``` engine.block.setFill(block, fill = solidColor) ``` ``` fun supportsFill(block: DesignBlock): Boolean ``` Query if the given block has fill color properties. * `block`: the block to query. * Returns true if the block has fill color properties, false otherwise. ``` engine.block.supportsFill(block) ``` ``` fun setFillEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the fill of the given design block. Required scope: "fill/change" * `block`: the block whose fill should be enabled or disabled. * `enabled`: if true, the fill will be enabled. ``` engine.block.setFillEnabled(block, enabled = false) ``` ``` fun isFillEnabled(block: DesignBlock): Boolean ``` Query if the fill of the given design block is enabled. * `block`: the block whose fill state should be queried. * Returns true if the fill is enabled, false otherwise. ``` engine.block.isFillEnabled(block) ``` Using the Apparel Editor - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/solutions/apparel-editor?language=kotlin&platform=android#configuration # Using the Apparel Editor `Apparel Editor` is a custom, mobile apparel UI for creating a print-ready design. The editable page is overlaid on a t-shirt mockup to give users an idea of where to position elements. Toggling from edit to preview mode allows reviewing the design in the context of the entire t-shirt. A floating action button at the bottom of the editor features only the most essential editing options in order of relevance allowing users to overlay text, add images, shapes, stickers and upload new image assets. In this example, we will show you how to initialize the `Apparel Editor` solution for the mobile editor on Android. The mobile editor is implemented entirely with Jetpack Compose and this example assumes that you use compose navigation, however, you can check the `Activity` and `Fragment` implementation samples on the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page. They can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-apparel-editor/ApparelEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-apparel-editor/ApparelEditorSolution.kt) ## Configuration In order to launch the `Apparel Editor`, you should initialize `EngineConfiguration` object. It is responsible for the configuration of the [Engine](/docs/cesdk/engine/quickstart/). All the properties other than the `onCreate` property have default values, so you do not have to worry about initializing them unless you need. Moreover, there is an `EngineConfiguration.rememberForApparel` helper function that only requires the `license` property. Note that you can override the `scene` that should be loaded in the `Apparel Editor` if you use the helper function. ``` val engineConfiguration = EngineConfiguration.rememberForApparel( license = "", userId = "", ) ``` Optionally, you can also provide an `EditorConfiguration` object if you want to configure the UI behavior of the `Apparel Editor`. If you do not provide a custom implementation, the editor will be launched with `EditorConfiguration.getDefault()` configuration object. ``` val editorConfiguration = EditorConfiguration.rememberForApparel() ``` For more details on how to use the configuration objects, visit [configuration](/docs/cesdk/mobile-editor/configuration/) page. ## Initialization After creating the configuration objects, launching the editor is straightforward. Just invoke the `ApparelEditor` composable function from your composable context and pass the configuration objects that you have created. In addition, you also need to provide an implementation for the `onClose` callback. That defines what should happen when the editor close event is triggered. If you are not using compose navigation, you can check `Activity` and `Fragment` implementation samples in the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page for `onClose` implementations. ``` ApparelEditor( engineConfiguration = engineConfiguration, editorConfiguration = editorConfiguration, ) { // You can set result here navController.popBackStack() } ``` How to Use the Camera - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/using-camera?language=kotlin&platform=android#setup # How to Use the Camera Other than having pre-recorded [video](/docs/cesdk/engine/guides/video/) in your scene you can also have a live preview from a camera in the engine. This allows you to make full use of the engine's capabilities such as [effects](/docs/cesdk/engine/api/block-effects/), [strokes](/docs/cesdk/engine/api/block-strokes/) and [drop shadows](/docs/cesdk/engine/api/block-drop-shadow/), while the preview integrates with the composition of your scene. Simply swap out the `VideoFill` of a block with a `PixelStreamFill`. This guide shows you how the `PixelStreamFill` can be used in combination with a camera. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-camera) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-camera) ## Setup This example uses the Engine with SurfaceView as it does not make sense to use camera preview in offscreen mode. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. Before starting the implementation, we are going to need couple more dependencies other than the Engine itself. Camera preview implementation is based on the android library [camerax](https://developer.android.com/training/camerax), therefore, we should include all required camerax dependencies to our project. You can check all available dependencies [here](https://developer.android.com/training/camerax/architecture). Other than camerax, we also need to include the Engine camera extension dependency. This dependency provides API for a single line bridging between camerax and the Engine. It is important that we use camerax version >= 1.1.0 in order to avoid unexpected crashes due to API signature changes. Also, it is highly recommended to always use the exact same version for both `engine` and `engine-camera` dependencies. ``` implementation "ly.img:engine-camera:1.43.0" implementation "androidx.camera:camera-core:1.2.3" implementation "androidx.camera:camera-camera2:1.2.3" implementation "androidx.camera:camera-view:1.2.3" implementation "androidx.camera:camera-lifecycle:1.2.3" implementation "androidx.camera:camera-video:1.2.3" ``` Now we have all the required dependencies to work with the camera. We instantiate all required camerax objects in order to start previewing. You can check all available camerax preview configurations [here](https://developer.android.com/training/camerax/architecture). ``` val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() val preview = Preview.Builder().build() val qualitySelector = QualitySelector.from(Quality.FHD) val recorder = Recorder.Builder() .setQualitySelector(qualitySelector) .build() val videoCapture = VideoCapture.Builder(recorder) .setMirrorMode(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY) .build() cameraProvider.bindToLifecycle(activity, cameraSelector, preview, videoCapture) ``` We create a video scene with a single page. Then we create a `PixelStreamFill` and assign it to the page. Then we connect camerax `Preview` and `PixelStreamFill` objects via `setCameraPreview` extension function that is provided by `engine-camera` dependency. To demonstrate the live preview capabilities of the engine we also apply an effect to the page. ``` val scene = engine.scene.createForVideo() val page = engine.block.create(DesignBlockType.Page) engine.block.appendChild(parent = scene, child = page) val pixelStreamFill = engine.block.createFill(FillType.PixelStream) engine.block.setFill(block = page, fill = pixelStreamFill) engine.setCameraPreview(pixelStreamFill, preview, mirrored = false) engine.block.appendEffect( block = page, effectBlock = engine.block.createEffect(EffectType.HalfTone), ) ``` ## Orientation To not waste expensive compute time by transforming the pixel data of the buffer itself, it's often beneficial to apply a transformation during rendering and let the GPU handle this work much more efficiently. For this purpose the `PixelStreamFill` has an `orientation` property. You can use it to mirror the image or rotate it in 90° steps. This property lets you easily mirror an image from a front facing camera or rotate the image by 90° when the user holds a device sideways. Note that its initial value is set in `setCameraPreview` based on camerax preview transformation info listener and `mirrored` flag. Available values are `Left`, `LeftMirrored`, `Down`, `DownMirrored`, `Right`, `RightMirrored`, `Up`, `UpMirrored`. ``` // If camerax preview transformation info rotation is 90, this will return Left. If we passed mirrored = true, this would be LeftMirrored. val orientation = engine.block.getEnum( block = pixelStreamFill, property = "fill/pixelStream/orientation", ) ``` ## Camera Camerax is a very powerful library and it allows video capturing, image capturing and other use cases. Note that the Engine does not limit usage of any of the camerax use cases: it only provides a mechanism to render camera preview into the Engine canvas. For demonstration purposes, we will proceed with video capture. We create a video capture session and start recording the frames into a temporary file in `filesDir`. Once the recording is finished we swap the `PixelStreamFill` with a `VideoFill` to play back the recorded video file. ``` val recordingFile = File(surfaceView.context.filesDir, "temp.mp4") val fileOutputOptions = FileOutputOptions.Builder(recordingFile).build() val recording = videoCapture.output .prepareRecording(activity, fileOutputOptions) .start(ContextCompat.getMainExecutor(surfaceView.context)) { if (it !is VideoRecordEvent.Finalize) return@start val videoFill = engine.block.createFill(FillType.Video) engine.block.setFill(block = page, fill = videoFill) engine.block.setString( block = videoFill, property = "fill/video/fileURI", value = recordingFile.toUri().toString(), ) } delay(5000L) recording.stop() ``` Cutout - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-cutout?language=kotlin&platform=android#setup # Cutout In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine `block` and `editor` API to create and modify a cutout block. Cutout blocks can be created from an SVG path or by performing boolean operations on existing multiple cutout blocks. One can then change a cutout's offset from its underlying path and its type. By default, a cutout's color is set by a spot color. Cutouts of type `CutoutType.SOLID` use the spot color `"CutContour"` and cutouts of type `CutoutType.DASHED` use the spot color `"PerfCutContour"`. One can change these defaults to other spot colors. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun createCutoutFromPath(path: String): DesignBlock ``` Create a Cutout block. * `path`: an SVG string describing a path. * Returns the handle of the created Cutout. ``` val circle = engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z") ``` ``` fun createCutoutFromBlocks( blocks: List, vectorizeDistanceThreshold: Float = 2F, simplifyDistanceThreshold: Float = 4F, useExistingShapeInformation: Boolean = true, ): DesignBlock ``` Create a cutout block whose path will be the contour of the given blocks. The cutout path for each block is derived from one of the two ways: * Blocks that have already have a vector path (shapes, SVG-based text or stickers). * Blocks that don't have a vector path are vectorized and then, optionally, simplified to eliminate jaggedness (images). * `blocks`: the blocks whose shape will serve as the basis for the cutout's path. * `vectorizeDistanceThreshold`: the maximum number of pixels by which the cutout's path can deviate from the original contour. Default value is 2F. * `simplifyDistanceThreshold`: the maximum number of pixels by which the simplified cutout path can deviate from the cutout contour. If 0, no simplification step is performed. Default value is 4F. * `useExistingShapeInformation`: If true, the existing vector paths of the provided blocks will be used to create the cutout. If false, new shape information for the cutout will be generated. * Returns the handle of the created cutout. ``` // Create a cutout from the outlines of existing blocks. val mixed = engine.block.createCutoutFromBlocks(listOf(textBlock)) ``` ``` fun createCutoutFromOperation( blocks: List, op: CutoutOperation, ): DesignBlock ``` Perform a boolean operation on the given Cutout blocks. The cutout offset of the new block is 0. The cutout type of the new block is that of the first block. When performing a `CutoutOperation.DIFFERENCE` operation, the first block is the block subtracted from. * `blocks`: the blocks with which to perform the operation. * `op`: the boolean operation to perform. * Returns the newly created block. ``` val unionCircleSquare = engine.block.createCutoutFromOperation(listOf(circle, square), op = CutoutOperation.UNION) val differenceCircleSquare = engine.block.createCutoutFromOperation(listOf(circle, square), op = CutoutOperation.DIFFERENCE) val intersectionCircleSquare = engine.block.createCutoutFromOperation(listOf(circle, square), op = CutoutOperation.INTERSECTION) val xorCircleSquare = engine.block.createCutoutFromOperation(listOf(circle, square), op = CutoutOperation.XOR) ``` ``` fun setSpotColorForCutoutType( type: CutoutType, name: String, ) ``` Set the spot color assign to a cutout type. If no spot color is set, type `CutoutType.SOLID` is assigned "CutContour" and type `CutoutType.DASHED` is assigned "PerfCutContour". All cutout blocks of the given type will be immediately assigned that spot color. * `type`: the cutout type. * `name`: the spot color name to assign. ``` engine.block.setSpotColorForCutoutType(type = CutoutType.DASHED, name = "Yellow") ``` ``` fun getSpotColorForCutoutType(type: CutoutType): String ``` Get the name of the spot color assigned to a cutout type. * `type`: the cutout type. * Returns the color spot name. ``` val dashedSpotColor = engine.block.getSpotColorForCutoutType(type = CutoutType.DASHED) // "Yellow" ``` Managing Colors - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/colors?language=kotlin&platform=android#rgb # Managing Colors When specifying a color property, you can use one of three color spaces: [RGB](https://en.wikipedia.org/wiki/RGB_color_model), [CMYK](https://en.wikipedia.org/wiki/CMYK_color_model) and [spot color](https://en.wikipedia.org/wiki/Spot_color). The following properties can be set with the function `setColor` and support all three color spaces: * `'backgroundColor/color'` * `'camera/clearColor'` * `'dropShadow/color'` * `'fill/color/value'` * `'stroke/color'` ### RGB RGB is the color space used when rendering a color to a screen. All values of `R`, `G`, `B`must be between `0.0` and `1.0` When using RGB, you typically also specify opacity or alpha as a value between `0.0` and `1.0` and is then referred to as `RGBA`. When a RGB color has an alpha value that is not `1.0`, it will be rendered to screen with corresponding transparency. ### CMYK CMYK is the color space used when color printing. All values of `C`, `M`, `Y`, and `K` must be between `0.0` and `1.0` When using CMYK, you can also specify a tint value between `0.0` and `1.0`. When a CMYK color has a tint value that is not `1.0`, it will be rendered to screen as if transparent over a white background. When rendering to screen, CMYK colors are first converted to RGB using a simple mathematical conversion. Currently, the same conversion happens when exporting a scene to a PDF file. ### Spot Color Spot colors are typically used for special printers or other devices that understand how to use them. Spot colors are defined primarily by their name as that is the information that the device will use to render. For the purpose of rendering a spot color to screen, it must be given either an RGB or CMYK color approximation or both. These approximations adhere to the same restrictions respectively described above. You can specify a tint as a value between `0.0` and `1.0` which will be interpreted as opacity when rendering to screen. It is up to the special printer or device how to interpret the tint value. You can also specify an external reference, a string describing the origin of this spot color. When rendering to screen, the spot color's RGB or CMYK approximation will be used, in that order of preference. When exporting a scene to a PDF file, spot colors will be saved as a [Separation Color Space](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.6.pdf#G9.1850648). Using a spot color is a two step process: 1. you define a spot color with its name and color approximation(s) in the spot color registry. 2. you instantiate a spot color with its name, a tint and an external reference. The spot color registry allows you to: * list the defined spot colors * define a new a spot color with a name and its RGB or CMYK approximation * re-define an existing spot color's RGB or CMYK approximation * retrieve the RGB or CMYK approximation of an already defined spot color * remove a spot color from the list of defined spot colors Multiple blocks and their properties can refer to the same spot color and each can have a different tint and external reference. #### Warning If a block's color property refers to an undefined spot color, the default color magenta with an RGB approximation of (1, 0, 1) will be used. ### Converting between colors A utility function `convertColorToColorSpace` is provided to create a new color from an existing color and a new color space. RGB and CMYK colors can be converted between each other and is done so using a simple mathematical conversion. Spot colors can be converted to RGB and CMYK simply by using the corresponding approximation. RGB and CMYK colors cannot be converted to spot colors. ### Custom Color Libraries You can configure CE.SDK with custom color libraries. More information is found [here](/docs/cesdk/ui/guides/custom-color-libraries/). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Colors&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Colors&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors) ## Setup the scene We first create a new scene with a graphic block that has color fill. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) val block = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(block, shape = engine.block.createShape(ShapeType.Rect)) engine.block.setPositionX(block, value = 350F) engine.block.setPositionY(block, value = 400F) engine.block.setWidth(block, value = 100F) engine.block.setHeight(block, value = 100F) val fill = engine.block.createFill(FillType.Color) engine.block.setFill(block, fill = fill) ``` ## Create colors Here we instantiate a few colors with RGB and CMYK color spaces. We also define two spot colors, one with an RGB approximation and another with a CMYK approximation. Note that a spot colors can have both color space approximations. ``` val rgbaBlue = Color.fromRGBA(r = 0F, g = 0F, b = 1F, a = 1F) val cmykRed = Color.fromCMYK(c = 0F, m = 1F, y = 1F, k = 0F, tint = 1F) val cmykPartialRed = Color.fromCMYK(c = 0F, m = 1F, y = 1F, k = 0F, tint = 0.5F) engine.editor.setSpotColor( name = "Pink-Flamingo", Color.fromRGBA(r = 0.988F, g = 0.455F, b = 0.992F), ) engine.editor.setSpotColor(name = "Yellow", Color.fromCMYK(c = 0F, m = 0F, y = 1F, k = 0F)) val spotPinkFlamingo = Color.fromSpotColor( name = "Pink-Flamingo", tint = 1F, externalReference = "Crayola", ) val spotPartialYellow = Color.fromSpotColor(name = "Yellow", tint = 0.3F) ``` ## Applying colors to a block We can use the defined colors to modify certain properties of a fill or properties of a shape. Here we apply it to `'fill/color/value'`, `'stroke/color'` and `'dropShadow/color'`. ``` engine.block.setColor(fill, property = "fill/color/value", value = rgbaBlue) engine.block.setColor(fill, property = "fill/color/value", value = cmykRed) engine.block.setColor(block, property = "stroke/color", value = cmykPartialRed) engine.block.setColor(fill, property = "fill/color/value", value = spotPinkFlamingo) engine.block.setColor(block, property = "dropShadow/color", value = spotPartialYellow) ``` ## Converting colors Using the utility function `convertColorToColorSpace` we create a new color in the CMYK color space by converting the `rgbaBlue` color to the CMYK color space. We also create a new color in the RGB color space by converting the `spotPinkFlamingo` color to the RGB color space. ``` val cmykBlueConverted = engine.editor.convertColorToColorSpace( rgbaBlue, colorSpace = ColorSpace.CMYK, ) val rgbaPinkFlamingoConverted = engine.editor.convertColorToColorSpace( spotPinkFlamingo, colorSpace = ColorSpace.SRGB, ) ``` ## Listing spot colors This function returns the list of currently defined spot colors. ``` engine.editor.findAllSpotColors() // ["Crayola-Pink-Flamingo", "Yellow"] ``` ## Redefine a spot color We can re-define the RGB and CMYK approximations of an already defined spot color. Doing so will change the rendered color of the blocks. We change it for the CMYK approximation of `'Yellow'` and make it a bit greenish. The properties that have `'Yellow'` as their spot color will change when re-rendered. ``` engine.editor.setSpotColor("Yellow", Color.fromCMYK(c = 0.2F, m = 0F, y = 1F, k = 0F)) ``` ## Removing the definition of a spot color We can undefine a spot color. Doing so will make all the properties still referring to that spot color (`'Yellow'` in this case) use the default magenta RGB approximation. ``` engine.editor.removeSpotColor("Yellow") ``` Basic Configuration Settings - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/basics?language=kotlin&platform=android#configuration # Basic Configuration Settings In this example, we will show you how to make basic configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-basics/BasicEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-basics/BasicEditorSolution.kt) ## Configuration All the basic configuration settings are part of the `EngineConfiguration`. * `license` - the license to activate the [Engine](/docs/cesdk/engine/quickstart/) with. ``` license = "", ``` * `userId` - an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. The default value is `null`. ``` userId = "", ``` * `baseUri` - is used to initialize the engine's [`basePath` setting](/docs/cesdk/engine/api/editor-settings/#settings) before the editor's [`onCreate` callback](/docs/cesdk/mobile-editor/configuration/callbacks/) is run. It is the foundational URI for constructing absolute paths from relative ones. For example, setting it to the Android assets directory allows loading resources directly from there: `file:///android_asset/`. This URI enables the loading of specific scenes or assets using their relative paths. The default value is pointing at the [versioned IMG.LY CDN](https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets) but it should be changed in production environments. ``` baseUri = Uri.parse("file:///android_asset/"), ``` * `sceneUri` - the [scene](/docs/cesdk/engine/guides/create-scene/) URI to load content within the editor. Note that this configuration is only available in `EngineConfiguration.rememberFor{solution-name}` helper functions. This URI is used to load the scene in `EdiorConfiguration.onCreate`, therefore, you can configure the scene you load without helper functions too: simply invoke `EditorDefaults.onCreate(engine, sceneUri, eventHandler)` in `EdiorConfiguration.onCreate`. By default, helper functions load the scenes that are available at `EdiorConfiguration.default{solution-name}Scene`. Normally, you should not modify the `sceneUri`, however, you may want to save/restore the editing progress for your customers. If that is the case, you should [save the scene](/docs/cesdk/engine/guides/save-scene/) in one of the [callbacks](/docs/cesdk/mobile-editor/configuration/callbacks/), then provide the URI of the newly saved scene when your customer opens the editor next time. ``` sceneUri = EngineConfiguration.defaultDesignSceneUri, ``` * `renderTarget` - the target which should be used by the [Engine](/docs/cesdk/engine/quickstart/) to render. The engine is able to render on both [SurfaceView](https://developer.android.com/reference/android/view/SurfaceView) and [TextureView](https://developer.android.com/reference/android/view/TextureView). The default value is `EngineRenderTarget.SURFACE_VIEW`. ``` renderTarget = EngineRenderTarget.SURFACE_VIEW, ``` Control Audio & Video - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-video/?platform=android#time-offset-and-duration # Control Audio & Video In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to configure and control audio and video through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Time Offset and Duration The time offset determines when a block becomes active during playback on the scene's timeline, and the duration decides how long this block is active. Page blocks are a special case in that they have an implicitly calculated time offset that is determined by their order and the total duration of their preceding pages. As with any audio/video-related property, not every block supports these properties. Use `supportsTimeOffset` and `supportsDuration` to check. ``` fun supportsTimeOffset(block: DesignBlock): Boolean ``` Returns whether the block has a time offset property. * `block`: the block to query. * Returns true, if the block has a time offset property. ``` engine.block.supportsTimeOffset(audio) ``` ``` fun setTimeOffset( block: DesignBlock, offset: Double, ) ``` Set the time offset of the given block relative to its parent. The time offset controls when the block is first active in the timeline. Note: The time offset is not supported by the page block. * `block`: the block whose time offset should be changed. * `offset`: the new time offset in seconds. ``` engine.block.setTimeOffset(audio, offset = 2.0) ``` ``` fun getTimeOffset(block: DesignBlock): Double ``` Get the time offset of the given block relative to its parent. * `block`: the block whose time offset should be queried. * Returns the time offset of the block. ``` engine.block.getTimeOffset(audio) /* Returns 2 */ ``` ``` fun supportsDuration(block: DesignBlock): Boolean ``` Returns whether the block has a duration property. * `block`: the block to query. * Returns true if the block has a duration property. ``` engine.block.supportsDuration(page) ``` ``` fun setDuration( block: DesignBlock, duration: Double, ) ``` Set the playback duration of the given block in seconds. The duration defines for how long the block is active in the scene during playback. If a duration is set on the page block, it becomes the duration source block. Note: The duration is ignored when the scene is not in "Video" mode. * `block`: the block whose duration should be changed. * `duration`: the new duration in seconds. ``` engine.block.setDuration(page, duration = 10.0) ``` ``` fun getDuration(block: DesignBlock): Double ``` Get the playback duration of the given block in seconds. * `block`: the block whose duration should be returned. * Returns the block's duration. ``` engine.block.getDuration(page) /* Returns 10 */ ``` ``` fun supportsPageDurationSource( page: DesignBlock, block: DesignBlock, ): Boolean ``` Returns whether the block can be marked as the element that defines the duration of the given page. * `page`: the page block for which to query for. * `block`: the block to query. * Returns true, if the block can be marked as the element that defines the duration of the given page. ``` engine.block.supportsPageDurationSource(page, block) ``` ``` fun setPageDurationSource( page: DesignBlock, block: DesignBlock, ) ``` Set an block as duration source so that the overall page duration is automatically determined by this. If no defining block is set, the page duration is calculated over all children. Only one block per page can be marked as duration source. Will automatically unmark the previously marked. Note: This is only supported for blocks that have a duration. * `page`: the page block for which it should be enabled. * `block`: the block which should be marked as duration source. ``` engine.block.setPageDurationSource(page, block) ``` ``` fun isPageDurationSource(block: DesignBlock): Boolean ``` Returns whether the block is a duration source block. * `block`: the block whose duration source property should be queried. * Returns if the block is a duration source for a page. ``` engine.block.isPageDurationSource(block) ``` ``` fun removePageDurationSource(block: DesignBlock) ``` Remove the block as duration source block for the page. If a scene or page given set as block, it is deactivated for all blocks in the scene or page. * `block`: the block whose duration source property should be removed. ``` engine.block.removePageDurationSource(page) ``` ## Trim You can select a specific range of footage from your audio/video resource by providing a trim offset and a trim length. The footage will loop if the trim's length is shorter than the block's duration. This behavior can also be disabled using the `setLooping` function. ``` fun supportsTrim(block: DesignBlock): Boolean ``` Returns whether the block has trim properties. * `block`: the block to query. * Returns true, if the block has trim properties. ``` engine.block.supportsTrim(videoFill) ``` ``` fun setTrimOffset( block: DesignBlock, offset: Double, ) ``` Set the trim offset of the given block or fill. Sets the time in seconds within the fill at which playback of the audio or video clip should begin. Note: This requires the video or audio clip to be loaded. * `block`: the block whose trim should be updated. * `offset`: the new trim offset in seconds. ``` engine.block.setTrimOffset(videoFill, offset = 1.0) ``` ``` fun getTrimOffset(block: DesignBlock): Double ``` Get the trim offset of this block. Note: This requires the video or audio clip to be loaded. * `block`: the block whose trim offset should be queried. * Returns the trim offset in seconds. ``` engine.block.getTrimOffset(videoFill) /* Returns 1 */ ``` ``` fun setTrimLength( block: DesignBlock, length: Double, ) ``` Set the trim length of the given block or fill. The trim length is the duration of the audio or video clip that should be used for playback. Note: After reaching this value during playback, the trim region will loop. Note: This requires the video or audio clip to be loaded. * `block`: the object whose trim length should be updated. * `block`: the new trim length in seconds. ``` engine.block.setTrimLength(videoFill, length = 5.0) ``` ``` fun getTrimLength(block: DesignBlock): Double ``` Get the trim length of the given block or fill. * `block`: the object whose trim length should be queried. * Returns the trim length of the object. ``` engine.block.getTrimLength(videoFill) /* Returns 5 */ ``` ## Playback Control You can start and pause playback and seek to a certain point on the scene's timeline. There's also a solo playback mode to preview audio and video blocks individually while the rest of the scene stays frozen. Finally, you can enable or disable the looping behavior of blocks and control their audio volume. ``` fun setPlaying( block: DesignBlock, enabled: Boolean, ) ``` Set whether the block should be during active playback. * `block`: the block that should be updated. * `enabled`: whether the block should be playing its contents. ``` engine.block.setPlaying(page, enabled = true) ``` ``` fun isPlaying(block: DesignBlock): Boolean ``` Returns whether the block is currently during active playback. * `block`: the block to query. * Returns whether the block is during playback. ``` engine.block.isPlaying(page) ``` ``` fun setSoloPlaybackEnabled( block: DesignBlock, enabled: Boolean, ) ``` Set whether the given block or fill should play its contents while the rest of the scene remains paused. Note: Setting this to true for one block will automatically set it to false on all other blocks. * `block`: the block or fill to update. * `enabled`: whether the block's playback should progress as time moves on. ``` engine.block.setSoloPlaybackEnabled(videoFill, enabled = true) ``` ``` fun isSoloPlaybackEnabled(block: DesignBlock): Boolean ``` Return whether the given block or fill is currently set to play its contents while the rest of the scene remains paused. * `block`: the block or fill to query. * Returns whether solo playback is enabled for this block. ``` engine.block.isSoloPlaybackEnabled(videoFill) ``` ``` fun supportsPlaybackTime(block: DesignBlock): Boolean ``` Returns whether the block has a playback time property. * `block`: the block to query. * Returns whether the block has a playback time property. ``` engine.block.supportsPlaybackTime(page) ``` ``` fun setPlaybackTime( block: DesignBlock, time: Double, ) ``` Set the playback time of the given block. * `block`: the block whose playback time should be updated. * `time`: the new playback time of the block in seconds. ``` engine.block.setPlaybackTime(page, time = 1.0) ``` ``` fun getPlaybackTime(block: DesignBlock): Double ``` Get the playback time of the given block. * `block`: the block to query. * Returns the playback time of the block in seconds. ``` engine.block.getPlaybackTime(page) ``` ``` fun isVisibleAtCurrentPlaybackTime(block: DesignBlock): Boolean ``` Returns whether the block should be visible on the canvas at the current playback time. * `block`: the block to query. * Returns the visibility state if the query succeeded. ``` engine.block.isVisibleAtCurrentPlaybackTime(block) ``` ``` fun supportsPlaybackControl(block: DesignBlock): Boolean ``` Returns whether the block supports a playback control. * `block`: the block to query. * Returns whether the block has playback control. ``` engine.block.supportsPlaybackControl(videoFill) ``` ``` fun setLooping( block: DesignBlock, looping: Boolean, ) ``` Set whether the block should start from the beginning again or stop. * `block`: the block or video fill to update. * Returns whether the block should loop to the beginning or stop. ``` engine.block.setLooping(videoFill, looping = true) ``` ``` fun isLooping(block: DesignBlock): Boolean ``` Query whether the block is looping. * `block`: the block to query. * Returns whether the block is looping. ``` engine.block.isLooping(videoFill) ``` ``` fun setMuted( block: DesignBlock, muted: Boolean, ) ``` Set whether the audio of the block is muted. * `block`: the block or video fill to update. * `muted`: whether the audio should be muted. ``` engine.block.setMuted(videoFill, muted = true) ``` ``` fun isMuted(block: DesignBlock): Boolean ``` Query whether the block is muted. * `block`: the block to query. * Returns whether the block is muted. ``` engine.block.isMuted(videoFill) ``` ``` fun setVolume( block: DesignBlock, @FloatRange(from = 0.0, to = 1.0) volume: Float, ) ``` Set the audio volume of the given block. * `block`: the block or video fill to update. * `volume`: the desired volume with a range of `0, 1`. ``` engine.block.setVolume(videoFill, volume = 0.5F) /* 50% volume */ ``` ``` @FloatRange(from = 0.0, to = 1.0) fun getVolume(block: DesignBlock): Float ``` Get the audio volume of the given block. * `block`: the block to query. * Returns volume with a range of `0, 1`. ``` engine.block.getVolume(videoFill) ``` ## Resource Control Until an audio/video resource referenced by a block is loaded, properties like the duration of the resource aren't available, and accessing those will lead to an error. You can avoid this by forcing the resource you want to access to load using `forceLoadAVResource`. ``` suspend fun forceLoadAVResource(block: DesignBlock) ``` Begins loading the required audio and video resource for the given video fill or audio block. * `block`: the video fill or audio block whose resource should be loaded. ``` engine.block.forceLoadAVResource(videoFill) ``` ``` @UnstableEngineApi fun isAVResourceLoaded(block: DesignBlock): Boolean ``` Returns whether the audio and video resource for the given video fill or audio block is loaded. Note that the function is unstable and mared with `UnstableEngineApi`. * `block`: the video fill or audio block. * Returns whether the resource is loaded. ``` engine.block.isAVResourceLoaded(videoFill) ``` ``` fun getAVResourceTotalDuration(block: DesignBlock): Double ``` Get the duration in seconds of the video or audio resource that is attached to the given block. * `block`: the video fill or audio block. * Returns the video or audio file duration. ``` engine.block.getAVResourceTotalDuration(videoFill) ``` ``` fun getVideoWidth(videoFill: DesignBlock): Int ``` Get the video width in pixels of the video resource that is attached to the given block. * `videoFill`: the video fill. * Returns the video width in pixels. ``` val videoWidth = engine.block.getVideoWidth(videoFill) ``` ``` fun getVideoHeight(videoFill: DesignBlock): Int ``` Get the video height in pixels of the video resource that is attached to the given block. * `videoFill`: the video fill. * Returns the video height in pixels. ``` val videoHeight = engine.block.getVideoHeight(videoFill) ``` ## Thumbnail Previews For a user interface, it can be helpful to have image previews in the form of thumbnails for any given video resource. For videos, the engine can provide one or more frames using `generateVideoThumbnailSequence`. Pass the video fill that references the video resource. In addition to video thumbnails, the engine can also render compositions of design blocks over time. To do this pass in the respective design block. The video editor uses these to visually represent blocks in the timeline. In order to visualize audio signals `generateAudioThumbnailSequence` can be used. This generates a sequence of values in the range of 0 to 1 that represent the loudness of the signal. These values can be used to render a waveform pattern in any custom style. Note: there can be at most one thumbnail generation request per block at any given time. If you don't want to wait for the request to finish before issuing a new request, you can cancel it by calling `cancel()` on the `Job` object returned on launching the flow. ``` fun generateVideoThumbnailSequence( block: DesignBlock, thumbnailHeight: Int, timeBegin: Double, timeEnd: Double, numberOfFrames: Int, ): Flow ``` Generate a thumbnail sequence for the given video fill or design block. * `block`: the video fill or a design block. * `thumbnailHeight`: the height of a thumbnail. The width will be calculated from the video aspect ratio. * `timeBegin`: the time in seconds relative to the time offset of the design block at which the thumbnail sequence should start. * `timeEnd`: the time in seconds relative to the time offset of the design block at which the thumbnail sequence should end. * `numberOfFrames`: the number of frames to generate. * Returns a flow of `VideoThumbnailResult` object which emits for every generated frame thumbnail. It emits exactly `numberOfFrames` times. ``` engine.block.generateVideoThumbnailSequence( block = videoFill, thumbnailHeight = 128, timeBegin = 0.5, timeEnd = 9.5, numberOfFrames = 10 ).onEach { println("frameIndex = ${it.frameIndex}, width = ${it.width}, height = ${it.height}, imageData:size = ${it.imageData.size}") }.launchIn(viewLifecycleScope) ``` ``` fun generateAudioThumbnailSequence( block: DesignBlock, samplesPerChunk: Int, timeBegin: Double, timeEnd: Double, numberOfSamples: Int, numberOfChannels: Int, ): Flow ``` Generate a thumbnail sequence for the given audio block or video fill. A thumbnail in this case is a chunk of samples in the range of 0 to 1. In case stereo data is requested, the samples are interleaved, starting with the left channel. * `block`: the audio block or video fill. * `samplesPerChunk`: the number of samples per chunk. * `timeBegin`: the time in seconds at which the thumbnail sequence should start. * `timeEnd`: the time in seconds at which the thumbnail sequence should end. * `numberOfSamples`: the total number of samples to generate. * `numberOfChannels`: the number of channels in the output. 1 for mono, 2 for stereo. * Returns a flow of `AudioThumbnailResult` object which emits numberOfSamples / samplesPerChunk times. ``` engine.block.generateAudioThumbnailSequence( block = audio, samplesPerChunk = 20, timeBegin = 0.5, timeEnd = 9.5, numberOfSamples = 10 * 20, numberOfChannels = 2, ).onEach { println("chunkIndex = ${it.chunkIndex}, samples:size = ${it.samples.size}") drawWavePattern(it.samples) }.launchIn(viewLifecycleScope) ``` Creating a Scene From an Initial Image URL - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-url/?platform=android&language=kotlin # Creating a Scene From an Initial Image URL In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url/CreateSceneFromImageURL.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url/CreateSceneFromImageURL.kt) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `suspend fun createFromImage(imageUri: URI, dpi: Float = 300F, pixelScaleFactor: Float = 1F): DesignBlock` and passing a URI as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. Create an instance of `Uri` using the remote url. Use the object `imageRemoteUri` as a source for the initial image. ``` val imageRemoteUri = Uri.parse("https://img.ly/static/ubq_samples/sample_4.jpg") val scene = engine.scene.createFromImage(imageRemoteUri) ``` We can retrieve the graphic block id of this initial image using `fun findByType(blockType: DesignBlockType): List`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the image fill. val block = engine.block.findByType(DesignBlockType.Graphic).first() ``` We can then manipulate and modify this block. Here we modify its opacity with `fun setOpacity(block: DesignBlock, @FloatRange(from = 0.0, to = 1.0) value: Float)`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, value = 0.5F) ``` When starting with an initial image, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Configure the Engine to use Assets served from your own Servers - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/assets-served-from-your-own-servers?language=kotlin&platform=android#1-register-imglys-default-assets Platform Web iOS Catalyst macOS Android Language Kotlin Platform: Android Language: Kotlin # Configure the Engine to use Assets served from your own Servers Learn how to serve assets from your own servers in the Engine. In this example, we explain how to configure the Creative Engine to use assets hosted on your own servers. While we serve all assets from our own CDN by default, it is highly recommended to serve the assets from your own servers in a production environment. ## 1\. Register IMG.LY's default assets If you want to use our default asset sources in your integration, call `fun Engine.addDefaultAssetSources(baseUri: Uri, exclude: Set)`. Right after initialization: ``` val engine = Engine("ly.img.engine.example") engine.start() engine.addDefaultAssetSources() ``` This call adds IMG.LY's default asset sources for stickers, vectorpaths and filters to your engine instance. By default, these include the following sources represented by `DefaultAssetSource` enum\`: * `DefaultAssetSource.STICKER` - `ly.img.sticker` - Various stickers. * `DefaultAssetSource.VECTOR_PATH` - `ly.img.vectorpath` - Shapes and arrows. * `DefaultAssetSource.FILTER_LUT` - `ly.img.filter.lut` - LUT effects of various kinds. * `DefaultAssetSource.FILTER_DUO_TONE` - `ly.img.filter.duotone` - Color effects of various kinds. * `DefaultAssetSource.COLORS_DEFAULT_PALETTE` - `ly.img.colors.defaultPalette` - Default color palette. * `DefaultAssetSource.EFFECT` - `ly.img.effect` - Default effects. * `DefaultAssetSource.BLUR` - `ly.img.blur` - Default blurs. * `DefaultAssetSource.TYPEFACE` - `ly.img.typeface` - Default typefaces. If you don't specify a `baseUri` option, the assets are parsed and served from the IMG.LY CDN. It is highly recommended to serve the assets from your own servers in a production environment, if you decide to use them. To do so, follow the steps below and pass a `baseUri` option to `addDefaultAssetSources`. If you only need a subset of the IDs above, use the `exclude` option to pass a list of ignored `DefaultAssetSource` objects\`. ## 2\. Copy Assets Download the IMG.LY default assets from [our CDN](https://cdn.img.ly/assets/v2/IMGLY-Assets.zip). Copy the extracted folders to your own CDN server or to the android assets folder if you want to use them offline. It can be on the root or any subfolder. In this example we place them in assets subfolder. ## 3\. Configure the IMGLYEngine to use your self-hosted assets Next, we need to configure the SDK to use the copied assets instead of the ones served via IMG.LY CDN. `Engine.addDefaultAssetSources` offers a `baseUri` option, that needs to be set to an absolute Uri, pointing to your newly added assets. In case you have copied to your own cdn path: ``` val baseUri = Uri.parse("https://cdn.your.custom.domain/assets") engine.addDefaultAssetSources(baseUri) ``` In case you have copied to android assets folder: ``` val baseUri = Uri.parse("file:///android_asset/assets") engine.addDefaultAssetSources(baseUri) ``` [ Previous Using the Camera ](/docs/cesdk/engine/guides/using-camera/)[ Next Adding Custom Asset Sources ](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) Integrate the Mobile Editor - CE.SDK | IMG.LY Docs [android/activity/kotlin] https://img.ly/docs/cesdk/mobile-editor/quickstart/?platform=android&language=kotlin&framework=activity # Integrate the Mobile Editor In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile editor in your Android app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) ## Adding dependency Add IMG.LY maven repository to the list of maven urls in the `settings.gradle` file. ``` maven { name "IMG.LY Artifactory" url "https://artifactory.img.ly/artifactory/maven" mavenContent { includeGroup("ly.img") } } ``` Add editor dependency in the `build.gradle` file of your application module. ``` implementation "ly.img:editor:1.43.0" // Add this as well if you want to use img.ly camera's powerful features. // If not, then system camera will be used everywhere. implementation "ly.img:camera:1.43.0" ``` ## Requirements In order to use the mobile editor, your application should meet the following requirements: * `buildFeatures.compose` should be `true`, as the editor is written in Jetpack Compose. ``` buildFeatures { compose true } ``` * `composeOptions.kotlinCompilerExtensionVersion` should match the kotlin version. Use the official compatibility map in [here](https://developer.android.com/jetpack/androidx/releases/compose-kotlin). ``` composeOptions { kotlinCompilerExtensionVersion = "1.5.3" } ``` * `compose-bom` version is `2023.05.01` or higher if your project uses Jetpack Compose dependencies. Note that using lower versions may cause crashes and issues in your own compose code, as our version will override yours. In case you are not using BOM, you can find the BOM to compose library version mapping in [here](https://developer.android.com/jetpack/compose/bom/bom-mapping). ``` implementation(platform("androidx.compose:compose-bom:2023.05.01")) ``` * Kotlin version is 1.9.10 or higher. * `minSdk` is 24 (Android 7) or higher. ``` minSdk 24 ``` By default, the mobile editor supports following ABIs: `arm64-v8a`, `armeabi-v7a`, `x86_64` and `x86`. If you want to filter out some of the ABIs, use `abiFilters`. ``` ndk { abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" } ``` ## Usage In this example, the basic usage of the mobile editor is going to be demonstrated using the [Design Editor](/docs/cesdk/mobile-editor/solutions/design-editor/) solution, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). In order to launch the editor, an `EngineConfiguration` object should be provided. Use the `EngineConfiguration.rememberForDesign()` for the simplest implementation. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. #### Warning Other than the _userId_ we also use the [Settings.Secure.ANDROID\_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) for better data accuracy. You should include it in the _Data safety form_ of your application when uploading it to the Play Store. ``` val engineConfiguration = EngineConfiguration.rememberForDesign( license = "", userId = "", ) ``` Afterwards, invoke `DesignEditor` composable function from your composable context. Other than the `EngineConfiguration`, you should also provide an `onClose` callback as the last parameter. That defines what should happen when the editor close event is triggered. ``` DesignEditor(engineConfiguration = engineConfiguration) { // You can set result here finish() } ``` That is all. Check all the available [solutions](/docs/cesdk/mobile-editor/solutions/) in order to decide which solution fits you best. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-editor/configuration/). Color - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-color?language=kotlin&platform=android#setup # Color In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to modify a fill block's solid color through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` fun setColor( block: DesignBlock, property: String, value: Color, ) ``` Set a color property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setColor(solidColor, property = "fill/color/value", value = rgbaBlack) ``` ``` fun getColor( block: DesignBlock, property: String, ): Color ``` Get the value of a color property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` val currentColor = engine.block.getColor(block, property = "fill/color/value") ``` Shapes - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-shapes?language=kotlin&platform=android#setup # Shapes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a `graphic` block's shape through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Shape To create a shape simply use `fun createShape(type: ShapeType): DesignBlock`. ``` val star = engine.block.createShape(ShapeType.Star) ``` ``` fun createShape(type: ShapeType): DesignBlock ``` Create a new shape, fails if type is unknown. * `type`: the type of the shape object that shall be created. * Returns the created shape's handle. ``` val star = engine.block.createShape(ShapeType.Star) ``` We currently support the following shape types: * `ShapeType.Rect` * `ShapeType.Line` * `ShapeType.Ellipse` * `ShapeType.Polygon` * `ShapeType.Star` * `ShapeType.VectorPath` ``` val star = engine.block.createShape(ShapeType.Star) ``` ## Functions You can configure shapes just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setInt(star, property = "shape/star/points", value = 6) ``` ``` fun getShape(block: DesignBlock): DesignBlock ``` Returns the block containing the shape properties of the given block. * `block`: the block whose shape block should be returned. * Returns the block that currently defines the given block's shape. ``` val previousShape = engine.block.getShape(block) ``` ## Destroying and Replacing Shapes Remember to first destroy the previous shape if you don't need it any more before changing the shape of a design block. ``` engine.block.destroy(previousShape) ``` ``` fun setShape( block: DesignBlock, shape: DesignBlock, ) ``` Sets the block containing the shape properties of the given block. Note: The previous shape block is not destroyed automatically. Required scope: "shape/change" * `block`: the block whose shape should be changed. * `shape`: the new shape. ``` engine.block.setShape(block, shape = star) ``` ``` fun supportsShape(block: DesignBlock): Boolean ``` Query if the given block has a shape property. * `block`: the block to query. * Returns true, if the block has a shape property, false otherwise. ``` engine.block.supportsShape(block) ``` Scene Lifecycle - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/scene-lifecycle?language=kotlin&platform=android#creating-a-scene # Scene Lifecycle In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to save scenes through the `scene` API and export them to PNG. At any time, the engine holds only a single scene. Loading or creating a scene will replace the current one. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Scene ``` fun create(sceneLayout: SceneLayout = SceneLayout.FREE): DesignBlock ``` Create a new scene, along with its own camera. * `sceneLayout`: the desired layout of the scene. * Returns a handle to the empty scene. ``` var scene = engine.scene.create() ``` ``` fun get(): DesignBlock? ``` Return the currently active scene. * Returns the scene or null, if none was created yet. ``` scene = engine.scene.get() ``` ``` fun createForVideo(): DesignBlock ``` Create a new scene in video mode, along with its own camera. * Returns a handle to the empty video scene. ``` scene = engine.scene.createForVideo() ``` ``` fun getMode(): SceneMode ``` Get the current scene mode. * Returns the current mode of the scene. ``` val sceneMode = engine.scene.getMode() // DESIGN or VIDEO ``` ``` suspend fun createFromImage( imageUri: Uri, dpi: Float = 300F, pixelScaleFactor: Float = 1F, sceneLayout: SceneLayout = SceneLayout.FREE, ): DesignBlock ``` Loads the given image and creates a scene with a single page showing the image. Fetching the image may take an arbitrary amount of time, so the scene isn't immediately available. * `imageUri`: the resource of the image file. * `dpi`: the DPI value to use when exporting and when converting between pixels and inches or millimeter units. * `pixelScaleFactor`: a scale factor that is applied to the final export resolution. * `sceneLayout`: the desired layout of the scene. * Returns a handle to the created scene. ``` scene = engine.scene.createFromImage(imageUri = Uri.parse("https://img.ly/static/ubq_samples/sample_4.jpg")) scene = engine.scene.createFromImage(imageUri = Uri.parse("file:///android_asset/images/sample_4.jpg")) scene = engine.scene.createFromImage(imageUri = Uri.fromFile(File(filesDir, "images/sample_4.jpg"))) ``` ``` suspend fun createFromVideo(videoUri: Uri): DesignBlock ``` Loads the given video and creates a scene with a single page showing the video. Fetching the video may take an arbitrary amount of time, so the scene isn't immediately available. * `videoUri`: the resource of the video file. * Returns a handle to the created scene. ``` scene = engine.scene.createFromVideo(videoUri = Uri.parse("https://img.ly/static/ubq_video_samples/bbb.mp4")) scene = engine.scene.createFromVideo(videoUri = Uri.parse("file:///android_asset/videos/bbb.mp4")) scene = engine.scene.createFromVideo(videoUri = Uri.fromFile(File(filesDir, "videos/bbb.mp4"))) ``` ## Loading a Scene ``` suspend fun load(scene: String): DesignBlock ``` Load the contents of a scene file. * `scene`: The scene file contents, a base64 string. * Returns the handle to the loaded scene. ``` suspend fun load(sceneUri: Uri): DesignBlock ``` Load a scene from the resource to the scene file. The scene file will be fetched asynchronously by the engine. * `sceneUri`: the resource of the scene file. * Returns the handle to the loaded scene. ``` scene = engine.scene.load(scene = "UBQ1ewoiZm9ybWF0Ij...") scene = engine.scene.load(sceneUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")) scene = engine.scene.load(sceneUri = Uri.parse("scenes/cesdk_postcard_1.scene")) scene = engine.scene.load(sceneUri = Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://$packageName/${R.raw.cesdk_postcard_1}")) scene = engine.scene.load(sceneUri = Uri.fromFile(File(filesDir, "scenes/cesdk_postcard_1.scene"))) ``` ``` suspend fun loadArchive(archiveUri: Uri): DesignBlock ``` Load the contents of a previously archived scene. * `archiveUri`: the resource of the scene archive file. * Returns the handle to the loaded scene. ``` scene = engine.scene.loadArchive(sceneUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1_scene.zip")) ``` ## Saving a Scene ``` suspend fun saveToString( scene: DesignBlock, allowedResourceSchemes: List = listOf("blob", "bundle", "file", "http", "https"), ): String ``` Serializes the current scene into a string. Selection is discarded. If a resource uri has a scheme that is not in `allowedResourceSchemes`, an exception will be thrown. * `scene`: the scene to serialize. * `allowedResourceSchemes`: the list of allowed resource schemes in the scene. * Returns a serialized scene string. ``` val string = engine.scene.saveToString() ``` ``` suspend fun saveToArchive(scene: DesignBlock): ByteBuffer ``` Saves the current scene and all of its referenced assets into an archive. The archive contains all assets, that were accessible when this function was called. Blocks in the archived scene reference assets relative from to the location of the scene file. These references are resolved when loading such a scene via `load`. * `scene`: the scene to archive. * Returns a serialized scene archive in zip format. ``` val archive = engine.scene.saveToArchive() ``` ## Events ``` fun onActiveChanged(): Flow ``` Subscribe to changes to be called whenever the active scene changes. This may happen upon scene load or creation of a new scene. * Returns flow of active scene change events. ``` val coroutineScope = CoroutineScope(Dispatchers.Main) ``` Exporting Blocks - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-export?language=kotlin&platform=android#setup # Exporting Blocks In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine export pages or blocks with the `block` API. Exporting allows fine-grained control of the target format. CE.SDK currently supports exporting as PNG, JPEG, TGA, BINARY, PDF and MP4. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Export a Static Design ``` suspend fun export( block: DesignBlock, mimeType: MimeType, options: ExportOptions? = null, onPreExport: suspend Engine.() -> Unit = {}, ): ByteBuffer ``` Exports a design block element as a file of the given mime type. Performs an internal update to resolve the final layout for the blocks. Note: The export happens in a background thread and the `Engine` instance in the `onPreExport` lambda is a separate instance and is alive until the suspending function resumes. Use this lambda to configure the background engine for export. * `block`: the design block element to export. * `mimeType`: the mime type of the output file. * `options`: the options for exporting the block type * `onPreExport`: the lambda to configure the engine before export. Note that the `Engine` parameter of this lambda is a separate engine that runs in the background. * Returns the exported data. ``` val exportOptions = options = ExportOptions( /** * The PNG compression level to use, when exporting to PNG. * Valid values are 0 to 9, higher means smaller, but slower. * Quality is not affected. * Ignored for other encodings. * The default value is 5. */ pngCompressionLevel = 5, /** * The JPEG quality to use when encoding to JPEG. * Valid values are (0F-1F], higher means better quality. * Ignored for other encodings. * The default value is 0.9F. */ jpegQuality = 0.9F, /** * The WebP quality to use when encoding to WebP. Valid values are (0-1], higher means better quality. * WebP uses a special lossless encoding that usually produces smaller file sizes than PNG. * Ignored for other encodings. Defaults to 1.0. */ webpQuality = 1.0F, /** * An optional target width used in conjunction with target height. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. * The default value is null. */ targetWidth = null, /** * An optional target height used in conjunction with target with. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. * The default value is null. */ targetHeight = null, /** * Export the PDF document with a higher compatibility to different PDF viewers. * Bitmap images and some effects like gradients will be rasterized with the DPI * setting instead of embedding them directly. * The default value is true. */ exportPdfWithHighCompatibility = true, /** * Export the PDF document with an underlayer. * An underlayer is generated by adding a graphics block behind the existing elements of the shape of the elements on * the page. */ exportPdfWithUnderlayer = false, /** * The name of the spot color to be used for the underlayer's fill. */ underlayerSpotColorName = "" /** * The adjustment in size of the shape of the underlayer. */ underlayerOffset = 0.0F ) val blob = engine.block.export( block = scene, mimeType = MimeType.PNG, options = exportOptions ) ``` ## Export with a Color Mask ``` suspend fun exportWithColorMask( block: DesignBlock, mimeType: MimeType, maskColor: RGBAColor, options: ExportOptions? = null, onPreExport: suspend Engine.() -> Unit = {}, ): Pair ``` Exports a design block element as a file of the given mime type. Performs an internal update to resolve the final layout for the blocks. Note: The export happens in a background thread and the `Engine` instance in the `onPreExport` lambda is a separate instance and is alive until the suspending function resumes. Use this lambda to configure the background engine for export. * `block`: the design block element to export. * `mimeType`: the mime type of the output file. * `maskColor`: the mask color. * `options`: the options for exporting the block type * `onPreExport`: the lambda to configure the engine before export. Note that the `Engine` parameter of this lambda is a separate engine that runs in the background. * Returns a pair where first is the exported image data and second is the mask data. ``` val colorMaskedBlob = engine.block.exportWithColorMask( block = scene, mimeType = MimeType.PNG, maskColor = Color.fromRGBA(r = 1F, g = 0F, b = 0F) options = exportOptions ) ``` ## Export a Video Export a page as a video file of the given mime type. ``` suspend fun exportVideo( block: DesignBlock, timeOffset: Double, duration: Double, mimeType: MimeType, progressCallback: (ExportVideoProgress) -> Unit, options: ExportVideoOptions? = null, onPreExport: suspend Engine.() -> Unit = {}, ): ByteBuffer ``` Exports a design block as a video file of the given mime type. Note: The export will run across multiple iterations of the update loop. In each iteration a frame is scheduled for encoding. Note: The export happens in a background thread and the `Engine` instance in the `onPreExport` lambda is a separate instance and is alive until the suspending function resumes. Use this lambda to configure the background engine for export. * `block`: the design block to export. Currently, only page blocks are supported. * `timeOffset`: the time offset in seconds of the page's timeline from which the video will start. * `duration`: the duration in seconds of the final video. * `mimeType`: the mime type of the output video file. * `progressCallback`: a callback that reports on the progress of the export. It informs the receiver of the number of frames rendered by the engine, the number of encoded frames, and the total number of frames to encode. * `options`: the options used for the export of the block. * `onPreExport`: the lambda to configure the engine before export. Note that the `Engine` parameter of this lambda is a separate engine that runs in the background. * Returns the exported video as a file in filesDir. Note that you are responsible for deleting the file after it is used. * Returns the exported data. ``` val videoExportOptions = ExportVideoOptions( /** * Determines the encoder feature set and in turn the quality, size and speed of the encoding process. * The default value is 77 (Main Profile). */ h264Profile = 77, /** * Controls the H.264 encoding level. This relates to parameters used by the encoder such as bit rate, * timings and motion vectors. Defined by the spec are levels 1.0 up to 6.2. To arrive at an integer value, * the level is multiplied by ten. E.g. to get level 5.2, pass a value of 52. * The default value is 52. */ h264Level = 52, /** * The video bitrate in bits per second. The maximum bitrate is determined by h264Profile and h264Level. * If the value is 0, the bitrate is automatically determined by the engine. */ videoBitrate = 0, /** * The audio bitrate in bits per second. If the value is 0, the bitrate is automatically determined by the engine (128kbps for stereo AAC stream). */ audioBitrate = 0, /** * The target frame rate of the exported video in Hz. * The default value is 30. */ frameRate = 30.0F, /** * An optional target width used in conjunction with target height. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. */ targetWidth = 1280, /** * An optional target height used in conjunction with target width. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. */ targetHeight = 720 ) val videoBlob = engine.block.exportVideo( block = page, timeOffset = 0.0, duration = engine.block.getDuration(page), mimeType = MimeType.MP4, progressCallback = { println("Rendered ${it.renderedFrames} frames and encoded ${it.encodedFrames} frames out of ${it.totalFrames} frames") } ) ``` ## Export Information Before exporting, the maximum export size and available memory can be queried. ``` fun getMaxExportSize(): Int ``` Get the export size limit in pixels on the current device. An export is only possible when both the width and height of the output are below or equal this limit. However, this is only an upper limit as the export might also not be possible due to other reasons, e.g., memory constraints. * Returns the upper export size limit in pixels or an unlimited size, i.e, the maximum signed 32-bit integer value, if the limit is unknown. ``` val maxExportSizeInPixels = engine.editor.getMaxExportSize() ``` ``` fun getAvailableMemory(): Long ``` Get the currently available memory in bytes. * Returns the currently available memory in bytes. ``` val availableMemoryInBytes = engine.editor.getAvailableMemory() ``` Selection & Visibility - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-selection-visibility?language=kotlin&platform=android#setup # Selection & Visibility In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Select blocks and change their visibility #### Note on Visibility A block can potentially be _invisible_ (in the sense that you can't see it), even though `isVisible()` returns true. This could be the case when a block has not been added to a parent, the parent itself is not visible, or the block is obscured by another block on top of it. ``` fun setSelected( block: DesignBlock, selected: Boolean, ) ``` Update the selection state of a block. Fails for invalid blocks. Note: Previously selected blocks remain selected. Required scope: "editor/select" * `block`: the block to query. * `selected`: whether or not the block should be selected. ``` engine.block.setSelected(block, selected = true) ``` ``` fun isSelected(block: DesignBlock): Boolean ``` Get the selected state of a block. * `block`: the block to query. * Returns true if the block is selected, false otherwise. ``` val isSelected = engine.block.isSelected(block) ``` ``` fun select(block: DesignBlock) ``` Selects the given block and deselects all other blocks. * `block`: the block to be selected. ``` engine.block.select(block) ``` ``` fun findAllSelected(): List ``` Get all currently selected blocks. * Returns An array of block ids. ``` val selectedIds = engine.block.findAllSelected() ``` ``` fun setVisible( block: DesignBlock, visible: Boolean, ) ``` Update a block's visibility. Required scope: "layer/visibility" * `block`: the block to update. * `visible`: whether the block shall be visible. ``` engine.block.setVisible(block, visible = true) ``` ``` fun isVisible(block: DesignBlock): Boolean ``` Query a block's visibility. * `block`: the block to query. * Returns true if visible, false otherwise. ``` val isVisible = engine.block.isVisible(block) ``` ``` fun setClipped( block: DesignBlock, clipped: Boolean, ) ``` Update a block's clipped state. Required scope: "layer/clipping" * `block`: the block to update. * `clipped`: whether the block should clips its contents to its frame. ``` engine.block.setClipped(page, clipped = true) ``` ``` fun isClipped(block: DesignBlock): Boolean ``` Query a block's clipped state. If `true`, the block should clip * `block`: the block to query. * Returns true if clipped, false otherwise. ``` val isClipped = engine.block.isClipped(page) ``` ``` fun onSelectionChanged(): Flow ``` Subscribe to changes in the current set of selected blocks. * Returns flow of selected block change events. ``` engine.block.onSelectionChanged() .onEach { println("Change in the set of selected blocks") } .launchIn(coroutineScope) ``` ``` fun onClicked(): Flow ``` Subscribe to block click events. Note: `DesignBlock` is emitted at the end of the engine update if it has been clicked. * Returns flow of block click events. ``` engine.block.onClicked() .onEach { println("Block $it was clicked") } .launchIn(coroutineScope) ``` ``` fun isIncludedInExport(block: DesignBlock): Boolean ``` Query if the given block is included on the exported result. * `block`: the block to query if it's included on the exported result. * Returns true, if the block is included on the exported result, false otherwise. ``` val isIncludedInExport = engine.block.isIncludedInExport(block) ``` ``` fun setIncludedInExport( block: DesignBlock, enabled: Boolean, ) ``` Set if you want the given design block to be included in exported result. * `block`: the block whose exportable state should be set. * `enabled`: if true, the block will be included on the exported result. ``` engine.block.setIncludedInExport(block, enabled = true) ``` Use of Emojis in Text - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/text-with-emojis?language=kotlin&platform=android#setup # Use of Emojis in Text Text blocks in CE.SDK support the use of emojis. A default emoji font is used to render these independently from the target platform. This guide shows how to change the default font and use emojis in text blocks. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-text-with-emojis/UsingFills.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-text-with-emojis/UsingFills.kt) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) engine.scene.zoomToBlock( page, paddingLeft = 40F, paddingTop = 40F, paddingRight = 40F, paddingBottom = 40F, ) ``` ## Change the Default Emoji Font The default font URI can be changed when another emoji font should be used or when the font should be served from another website, a content delivery network (CDN), or a local file path. The default is to use the [NotoColorEmoji](https://github.com/googlefonts/noto-emoji) font loaded from our [CDN](https://cdn.img.ly/assets/v2/emoji/NotoColorEmoji.ttf). This font file supports a wide variety of Emojis and is licensed under the [Open Font License](https://cdn.img.ly/assets/v2/emoji/LICENSE.txt). In order to change the URI, call the `fun setSettingString(keypath: string, value: string)` [Editor API](/docs/cesdk/engine/api/editor-change-settings/) with 'defaultEmojiFontFileUri' as keypath and the new URI as value. ``` val uri = engine.editor.getSettingString(keypath = "ubq://defaultEmojiFontFileUri") // From a bundle engine.editor.setSettingString( keypath = "ubq://defaultEmojiFontFileUri", value = "file:///android_asset/ly.img.cesdk/fonts/NotoColorEmoji.ttf", ) // From a URL engine.editor.setSettingString( keypath = "ubq://defaultEmojiFontFileUri", value = "https://cdn.img.ly/assets/v3/emoji/NotoColorEmoji.ttf", ) ``` ## Add a Text Block with an Emoji To add a text block with an emoji, add a text block and set the emoji as text content. ``` val text = engine.block.create(DesignBlockType.Text) engine.block.setString(text, property = "text/text", value = "Text with an emoji 🧐") engine.block.setWidth(text, value = 50F) engine.block.setHeight(text, value = 10F) engine.block.appendChild(parent = page, child = text) ``` Variables - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/variables?language=kotlin&platform=android#setup # Variables In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify variables through the `variable` API. The `variable` API lets you set or get the contents of variables that exist in your scene. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun findAll(): List ``` Get all text variables currently stored in the engine. * Returns a list of variable names. ``` val variableNames = engine.variable.findAll() ``` ``` fun set( key: String, value: String, ) ``` Set a text variable. * `key`: the variable's key. * `value`: the text to replace the variable with. ``` engine.variable.set(key = "name", value = "Chris") ``` ``` fun get(key: String): String ``` Get a text variable. * `key`: the variable's key. * Returns the text value of the variable. ``` val name = engine.variable.get(key = "name") // Chris ``` ``` fun remove(key: String) ``` Destroy a text variable. * `key`: the variable's key. ``` engine.variable.remove(key = "name") ``` ``` fun referencesAnyVariables(block: DesignBlock): Boolean ``` Checks whether the given block references any variables. Doesn't check the block's children. * `block`: the block to query. * Returns true if the block references variables, false otherwise. ``` engine.block.referencesAnyVariables(block) ``` ## Localizing Variable Keys (CE.SDK only) You can show localized labels for the registered variables to your users by adding a corresponding label property to the object stored at `i18n..variables..label` in the configuration. Otherwise, the name used in `variable.setString()` will be shown. ![](/docs/cesdk/53ae2ef96327bbd7ef5994cdfe73e792/variables-dark.png) Buffers - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/editor-buffers?language=kotlin&platform=android#setup # Buffers In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to create buffers through the `editor` API. Buffers can hold arbitrary data. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. #### Limitations Buffers are intended for temporary data only. * Buffer data is not part of the [scene serialization](/docs/cesdk/engine/api/scene-lifecycle/) * Changes to buffers can't be undone using the [history system](/docs/cesdk/engine/api/editor-history/) ``` fun createBuffer(): Uri ``` Create a resizable buffer that can hold arbitrary data. * Returns a uri to identify the buffer. ``` val audioBuffer = engine.editor.createBuffer() ``` ``` fun destroyBuffer(uri: Uri) ``` Destroy a buffer and free its resources. * `uri`: the uri of the buffer to destroy. ``` engine.editor.destroyBuffer(uri = audioBuffer) ``` ``` fun setBufferData( uri: Uri, offset: Int, data: ByteBuffer, ) ``` Set the data of a buffer. * `uri`: the uri of the buffer. * `offset`: the offset in bytes at which to start writing. * `data`: the data to write. Note that it has to be a direct `ByteBuffer`, created either via `ByteBuffer.allocateDirect` or via JNI NewDirectByteBuffer API. ``` // Generate 10 seconds of stereo 48 kHz audio data val sampleCount = 10 * 48000 val byteBuffer = ByteBuffer.allocate(2 * 4 * sampleCount) //2 channels, each 4 bytes repeat(sampleCount) { val sample = sin((440 * it * 2 * PI) / 48000).toFloat() byteBuffer.putFloat(sample) byteBuffer.putFloat(sample) } // Assign the audio data to the buffer val data = ByteArray(byteBuffer.capacity()) byteBuffer.position(0) byteBuffer.get(data) engine.editor.setBufferData(uri = audioBuffer, offset = 0, data = data) ``` ``` fun getBufferData( uri: Uri, offset: Int, length: Int, ): ByteBuffer ``` Get the data of a buffer. * `uri`: the uri of the buffer. * `offset`: the offset in bytes at which to start reading. * `length`: the number of bytes to read. * Returns the data read from the buffer or an error. ``` val chunk = engine.editor.getBufferData(uri = audioBuffer, offset = 0, length = 4096) ``` ``` fun setBufferLength( uri: Uri, length: Int, ) ``` Set the length of a buffer. * `uri`: the uri of the buffer. * `length`: the new length of the buffer in bytes. ``` engine.editor.setBufferLength(uri = audioBuffer, length = data.size / 2) ``` ``` fun getBufferLength(uri: Uri): Int ``` Get the length of a buffer. * `uri`: the uri of the buffer. * Returns the length of the buffer in bytes. ``` val length = engine.editor.getBufferLength(uri = audioBuffer) ``` Utils - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/editor-utils?language=kotlin&platform=android#setup # Utils In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to control URI lookups and perform color conversions in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## URI Resolver The `CreativeEngine` allows overriding how `URI`'s are resolved at runtime using the following APIs. See the [in-depth guide](/docs/cesdk/engine/guides/resolve-custom-uri/) for more details. ``` fun setUriResolver(resolver: ((Uri) -> Uri)?) ``` Sets a custom Uri resolver. This method can be called more than once. Subsequent calls will overwrite previous calls. To remove a previously set resolver, pass the value null. * `resolver`: custom resolution function which accepts both relative and absolute uri and returns absolute uri. ``` // Replace all .jpg files with the IMG.LY logo! engine.editor.setUriResolver { if (it.toString().endsWith(".jpg")) { return@setUriResolver Uri.parse("https://img.ly/static/ubq_samples/imgly_logo.jpg") } // Make use of the default Uri resolution behavior. engine.editor.defaultUriResolver(it) } ``` ``` fun getAbsoluteUri(uri: Uri): Uri ``` Resolves the given `uri`. If a custom resolver has been set with `setUriResolver`, it uses the custom resolver. Else, it resolves using `defaultUriResolver`. * `uri`: the Uri that should be resolved. * Returns the resolved absolute Uri. ``` // The custom resolver will return a path to the IMG.LY logo because the given path ends with ".jpg". // This applies regardless if the given path is relative or absolute. engine.editor.getAbsoluteUri(uri = Uri.parse("/banana.jpg")) ``` ``` fun defaultUriResolver(uri: Uri): Uri ``` This is the default implementation for the Uri resolver. If `uri` is absolute no adjustments are made. If it is relative, it resolves the given Uri relative to the `basePath` setting, which is "file:///android\_asset" by default (assets base uri). * `uri`: the Uri that should be resolved. * Returns the resolved absolute Uri. ``` engine.editor.defaultUriResolver(it) ``` ## Color Conversions To ease implementing advanced color interfaces, you may rely on the engine to perform color conversions. ``` fun convertColorToColorSpace( color: Color, colorSpace: ColorSpace, ): Color ``` Converts a color to the given color space. * `color`: the color to convert. * `colorSpace`: the color space to convert to. * Returns the converted color. ``` val rgbaGreen = Color.fromRGBA(r = 0F, g = 1F, b = 0F, a = 0F) val cmykGreen = engine.editor.convertColorToColorSpace(color = rgbaGreen, colorSpace = ColorSpace.CMYK) ``` ## Retrieving the mimetype of a resource ``` suspend fun getMimeType(uri: Uri): String ``` Returns the mimetype of the resources at the given Uri. If the resource is not already downloaded, this function will download it. * `uri`: the Uri of the resource. * Returns the mimetype of the resource. ``` val mimeType = engine.editor.getMimeType(Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.image/images/sample_1.jpg")) ``` ## Working with resources ``` fun findAllTransientResources(): List> ``` Returns the Uris and sizes of all resources whose data would be lost if the scene was exported. Note: This function is useful for determining which resources need to be relocated (e.g., to a CDN) before exporting a scene since the resources are not included in the exported scene. * Returns a list containing the Uris and sizes of transient resources. ``` val transientResources = engine.editor.findAllTransientResources() ``` ``` fun getResourceData( uri: Uri, chunkSize: Int, onData: (ByteBuffer) -> Boolean, ) ``` Provides the data of a resource at the given Uri. Note that it is a synchronous function and all the chunks are provided immediately before the function returns. * `uri`: the uri of the resource. * `chunkSize`: the size of the chunks in which the resource data is provided. * `onData`: the callback function that is called with the resource data. The callback will be called as long as there is data left to provide and the callback returns `true`. ``` val resourceUri = engine.block.findAllSelected()[0] .let { engine.block.getFill(it) } .let { engine.block.getString(it, property = "fill/image/imageFileURI") } .let { Uri.parse(it) } engine.editor.getResourceData(uri = resourceUri, chunkSize = 1024) { // ... true } ``` ``` fun relocateResource( currentUri: Uri, relocatedUri: Uri, ) ``` Changes the uri associated with a resource. Note: This function can be used change the Uri of a resource that has been relocated (e.g., to a CDN). * `currentUri`: the current Uri of the resource. * `relocatedUri`: the new Uri of the resource. ``` engine.editor.relocateResource(currentUri = resourceUri, relocatedUri = Uri.parse("http://your.cdn/image.png")) ``` Scene Contents - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/scene-contents?language=kotlin&platform=android#setup # Scene Contents Learn how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to explore scene contents through the `scene` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` fun getPages(): List ``` Get the sorted list of pages in the scene. * Returns the sorted list of pages in the scene. ``` val pages = engine.scene.getPages() ``` ``` fun getCurrentPage(): DesignBlock? ``` Get the current page, i.e., the page of the first selected element if this page is at least 25% visible or, otherwise, the page nearest to the viewport center. * Returns the current page in the scene or null. ``` val currentPage = engine.scene.getCurrentPage(); ``` ``` fun findNearestToViewPortCenterByType(blockType: DesignBlockType): List ``` Finds all blocks with the given type sorted by distance to viewport center. * `blockType`: the type to search for. * Returns a list of block ids sorted by distance to viewport center. ``` val nearestPageByType = engine.scene.findNearestToViewPortCenterByType(DesignBlockType.Page).first(); ``` ``` fun findNearestToViewPortCenterByKind(blockKind: String): List ``` Finds all blocks with the given kind sorted by distance to viewport center. * `blockKind`: the kind to search for. * Returns a list of block ids sorted by distance to viewport center. ``` val nearestImageByKind = engine.sce.findNearestToViewPortCenterByKind("image").first(); ``` ``` fun setDesignUnit(designUnit: DesignUnit) ``` Converts all values of the current scene into the given design unit. * `designUnit`: the new design unit of the scene. ``` engine.scene.setDesignUnit(DesignUnit.PIXEL) ``` ``` fun getDesignUnit(): DesignUnit ``` Returns the design unit of the current scene. * Returns The current design unit. ``` /* Now returns DesignUnit.PIXEL */ engine.scene.getDesignUnit() ``` How to use custom LUT filter - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/custom-lut-filter?language=kotlin&platform=android#creating-custom-filters # How to use custom LUT filter ## Creating Custom Filters We use a technology called Lookup Tables (LUTs) in order to add new filters to our SDK. The main idea is that colors respond to operations that are carried out during the filtering process. We 'record' that very response by applying the filter to the identity image shown below. ![Identity LUT](/docs/cesdk/7d624d6315581dca5652b65884e5e117/identity.png) The resulting image can be used within our SDK and the recorded changes can then be applied to any image by looking up the transformed colors in the modified LUT. If you want to create a new filter, you'll need to [download](/docs/cesdk/images/identity.png) the identity LUT shown above, load it into an image editing software of your choice, apply your operations, save it and add it to your app. > **WARNING:** As any compression artifacts in the edited LUT could lead to distorted results when applying the filter, you need to save your LUT as a PNG file. ## Using Custom Filters In this example, we will use a hosted CDN LUT filter file. First we will load one of our demo scenes and change the first image to use LUT filter we will provide. We will also configure the necessary setting based on the file. LUT file we will use: ![](https://cdn.img.ly/assets/v1/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png) Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-custom-lut-filter/CustomLUTFilter.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-custom-lut-filter/CustomLUTFilter.kt) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ## Load Scene After the setup, we create a new scene. Within this scene, we create a page, set its dimensions, and append it to the scene. Lastly, we adjust the zoom level to properly fit the page into the view. ``` val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 100F) engine.block.setHeight(page, value = 100F) engine.block.appendChild(parent = scene, child = page) engine.scene.zoomToBlock( scene, paddingLeft = 40F, paddingTop = 40F, paddingRight = 40F, paddingBottom = 40F, ) ``` ## Create Rectangle Next, we create a rectangle with defined dimensions and append it to the page. We will apply our LUT filter to this rectangle. ``` val rect = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(rect, shape = engine.block.createShape(ShapeType.Rect)) engine.block.setWidth(rect, value = 100F) engine.block.setHeight(rect, value = 100F) engine.block.appendChild(parent = page, child = rect) ``` ## Load Image After creating the rectangle, we create an image fill with a specified URL. This will load the image as a fill for the rectangle to which we will apply the LUT filter. ``` val imageFill = engine.block.createFill(FillType.Image) engine.block.setString( imageFill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_1.jpg", ) ``` ## Create LUT Filter Now, we create a Look-Up Table (LUT) filter effect. We enable the filter, set its intensity, and provide a URL for the LUT file. We also define the tile count for the filter. The LUT filter effect is then applied to the rectangle and image should appear black and white. ``` val lutFilter = engine.block.createEffect(EffectType.LutFilter) engine.block.setBoolean(lutFilter, property = "effect/enabled", value = true) engine.block.setFloat(lutFilter, property = "effect/lut_filter/intensity", value = 0.9F) engine.block.setString( lutFilter, property = "effect/lut_filter/lutFileURI", value = "https://cdn.img.ly/packages/imgly/cesdk-js/1.26.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png", ) engine.block.setInt(lutFilter, property = "effect/lut_filter/verticalTileCount", value = 5) engine.block.setInt(lutFilter, property = "effect/lut_filter/horizontalTileCount", value = 5) ``` ## Apply LUT Filter Finally, we apply the LUT filter effect to the rectangle, and set the image fill to the rectangle. Before setting an image fill, we destroy the default rectangle fill. ``` engine.block.appendEffect(rect, effectBlock = lutFilter) engine.block.setFill(rect, fill = imageFill) ``` Using Cutouts - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/cutouts?language=kotlin&platform=android#note # Using Cutouts Cutouts are a special feature one can use with cuttings printers. When printing a PDF file containing cutouts paths, a cutting printer will cut these paths with a cutter rather than print them with ink. Use cutouts to create stickers, iron on decals, etc. Cutouts can be created from an SVG string describing its underlying shape. Cutouts can also be created from combining multiple existing cutouts using the boolean operations `UNION`, `DIFFERENCE`, `INTERSECTION` and `XOR`. Cutouts have a type property which can take one of two values: `SOLID` and `DASHED`. Cutting printers recognize cutouts paths through their specially named spot colors. By default, `SOLID` cutouts have the spot color `"CutContour"` to produce a continuous cutting line and `DASHED` cutouts have the spot colors `"PerfCutContour"` to produce a perforated cutting line. You may need to adjust these spot color names for you printer. #### Note Note that the actual color approximation given to the spot color does not affect how the cutting printer interprets the cutout, only how it is rendered. The default color approximations are magenta for "CutContour" and green for "PerfCutContour". ​ Cutouts have an offset property that determines the distance at which the cutout path is rendered from the underlying path set when created. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Cutouts&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Cutouts&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts) ## Setup the scene We first create a new scene with a new page. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) ``` ## Create cutouts Here we add two cutouts. First, a circle of type `DASHED` and with an offset of 3.0. Second, a square of default type `SOLID` and an offset of 6.0. ``` val circle = engine.block.createCutoutFromPath( "M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z", ) engine.block.setFloat(circle, "cutout/offset", 3F) engine.block.setEnum(circle, "cutout/type", CutoutType.DASHED.key) val square = engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z") engine.block.setFloat(square, "cutout/offset", 6F) ``` ## Combining multiple cutouts into one Here we use the `UNION` operation to create a new cutout that consists of the combination of the earlier two cutouts we have created. Note that we destroy the previously created `circle` and `square` cutouts as we don't need them anymore and we certainly don't want to printer to cut through those paths as well. When combining multiple cutouts, the resulting cutout will be of the type of the first cutout given and an offset of 0. In this example, since the `circle` cutout is of type `DASHED`, the newly created cutout will also be of type `DASHED`. #### Warning When using the Difference operation, the first cutout is the cutout that is subtracted _from_. For other operations, the order of the cutouts don't matter. ​ ``` val union = engine.block.createCutoutFromOperation( listOf(circle, square), op = CutoutOperation.UNION, ) engine.block.destroy(circle) engine.block.destroy(square) ``` ## Change the default color for Solid cutouts For some reason, we'd like the cutouts of type `SOLID` to not render as magenta but as blue. Knowing that `"CutContour"` is the spot color associated with `SOLID`, we change it RGB approximation to blue. Thought the cutout will render as blue, the printer will still interpret this path as a cutting because of its special spot color name. ``` engine.editor.setSpotColor("CutContour", Color.fromRGBA(r = 0F, g = 0F, b = 1F, a = 1F)) ``` Save Scenes to an Archive - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/save-scene-to-archive?language=kotlin&platform=android#loading-scene-archives # Save Scenes to an Archive In this example, we will show you how to save scenes as an archive with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-save-scene-to-archive/SaveSceneToArchive.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-save-scene-to-archive/SaveSceneToArchive.kt) The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. As an archive, the resulting `Blob` includes all pages and any hidden elements and all the asset data. To get hold of such a `Blob`, you need to use `engine.scene.saveToArchive()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a `Blob` holding the entire scene currently loaded in the editor including its assets' data. ``` val blob = engine.scene.saveToArchive(scene = scene) ``` That `Blob` can then be treated as a form file parameter and sent to a remote location. ``` withContext(Dispatchers.IO) { val connection = URL("https://example.com/upload/").openConnection() as HttpURLConnection connection.requestMethod = "POST" connection.doOutput = true connection.outputStream.use { Channels.newChannel(it).write(blob) } connection.connect() } ``` ## Loading Scene Archives Loading a scene archives requires unzipping the archives contents to a location, that's accessible to the CreativeEngine. One could for example unzip the archive via `unzip archive.zip` and then serve its contents using `$ npx serve`. This spins up a local test server, that serves everything contained in the current directory at `http://localhost:3000` The archive can then be loaded by calling `engine.scene.load("http://localhost:3000/scene.scene")`. See [loading scenes](/docs/cesdk/engine/guides/load-scene-from-url/) for more details. All asset paths in the archive are then resolved relative to the location of the `scene.scene` file. For an image, that would result in `'http://localhost:3000/images/1234.jpeg'`. After loading all URLs are fully resolved with the location of the `scene.scene` file and the scene behaves like any other scene. ## Resolving assets from a different source The engine will use its [URI resolver](/docs/cesdk/engine/guides/resolve-custom-uri/) to resolve all asset paths it encounters. This allows you to redirect requests for the assets contained in archive to a different location. To do so, you can add a custom resolver, that redirects requests for assets to a different location. Assuming you store your archived scenes in a `scenes/` directory, this would be an example of how to do so: Save Scenes to a Blob - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/save-scene-to-blob?language=kotlin&platform=android#warning # Save Scenes to a Blob In this example, we will show you how to save scenes as a `Blob` with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-save-scene-to-blob/SaveSceneToBlob.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-save-scene-to-blob/SaveSceneToBlob.kt) The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. This is done by converting the contents of a scene to a string, which can then be stored or transferred. For sending these to a remote location, we wrap them in a `Blob` and treat it as a file object. #### Warning A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. To get hold of the scene contents as string, you need to use `engine.scene.saveToString()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements but none of the actual asset data. ``` val savedSceneString = engine.scene.saveToString(scene = scene) ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ``` val blob = savedSceneString.toByteArray(Charsets.UTF_8) ``` That object can then be treated as a form file parameter and sent to a remote location. ``` runCatching { withContext(Dispatchers.IO) { val connection = URL(uploadUrl).openConnection() as HttpURLConnection connection.requestMethod = "POST" connection.doOutput = true connection.outputStream.use { it.write(blob) } connection.connect() } } ``` Drop Shadow - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-drop-shadow?language=kotlin&platform=android#setup # Drop Shadow In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify an block's drop shadow through the `block` API. Drop shadows can be added to any shape, text or image. One can adjust its offset relative to its block on the X and Y axes, its blur factor on the X and Y axes and whether it is visible behind a transparent block. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun supportsDropShadow(block: DesignBlock): Boolean ``` Query if the given block has a drop shadow property. * `block`: the block to query. * Returns true if the block has a drop shadow property, false otherwise. ``` if (engine.block.supportsDropShadow(block)) { ``` ``` fun setDropShadowEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the drop shadow of the given design block. Required scope: "appearance/shadow" * `block`: the block whose drop shadow should be enabled or disabled. * `enabled`: if true, the drop shadow will be enabled. ``` engine.block.setDropShadowEnabled(block, enabled = true) ``` ``` fun isDropShadowEnabled(block: DesignBlock): Boolean ``` Query if the drop shadow of the given design block is enabled. * `block`: the block whose drop shadow should be queried. * Returns true if the block's drop shadow is enabled, false otherwise. ``` val dropShadowIsEnabled = engine.block.isDropShadowEnabled(block) ``` ``` fun setDropShadowColor( block: DesignBlock, color: Color, ) ``` Set the drop shadow color of the given design block. Required scope: "appearance/shadow" * `block`: the block whose drop shadow color should be set. * `color`: the color to set. ``` engine.block.setDropShadowColor(block, Color.fromRGBA(r = 1F, g = 0.75F, b = 0.8F, a = 1F)) ``` ``` fun getDropShadowColor(block: DesignBlock): Color ``` Get the drop shadow color of the given design block. * `block`: the block whose drop shadow color should be queried. * Returns the drop shadow color. ``` val dropShadowColor = engine.block.getDropShadowColor(block) ``` ``` fun setDropShadowOffsetX( block: DesignBlock, offsetX: Float, ) ``` Set the drop shadow's x offset of the given design block. Required scope: "appearance/shadow" * `block`: the block whose drop shadow's x offset should be set. * `offsetX`: the x offset to be set. ``` engine.block.setDropShadowOffsetX(block, offsetX = -10F) ``` ``` fun setDropShadowOffsetY( block: DesignBlock, offsetY: Float, ) ``` Set the drop shadow's y offset of the given design block. Required scope: "appearance/shadow" * `block`: the block whose drop shadow's y offset should be set. * `offsetY`: the y offset to be set. ``` engine.block.setDropShadowOffsetY(block, offsetY = 5F) ``` ``` fun getDropShadowOffsetX(block: DesignBlock): Float ``` Get the drop shadow's x offset of the given design block. * `block`: the block whose drop shadow's x offset should be queried. * Returns the offset. ``` val dropShadowOffsetX = engine.block.getDropShadowOffsetX(block); ``` ``` fun getDropShadowOffsetY(block: DesignBlock): Float ``` Get the drop shadow's y offset of the given design block. * `block`: the block whose drop shadow's y offset should be queried. * Returns the offset. ``` val dropShadowOffsetY = engine.block.getDropShadowOffsetY(block); ``` ``` fun setDropShadowBlurRadiusX( block: DesignBlock, blurRadiusX: Float, ) ``` Set the drop shadow's blur radius on the x axis of the given design block. Required scope: "appearance/shadow" * `block`: the block whose drop shadow's blur radius on the x axis should be set. * `blurRadiusX`: the blur radius to be set. ``` engine.block.setDropShadowBlurRadiusX(block, blurRadiusX = -10F) ``` ``` fun setDropShadowBlurRadiusY( block: DesignBlock, blurRadiusY: Float, ) ``` Set the drop shadow's blur radius on the y axis of the given design block. Required scope: "appearance/shadow" * `block`: the block whose drop shadow's blur radius on the y axis should be set. * `blurRadiusY`: the blur radius to be set. ``` engine.block.setDropShadowBlurRadiusY(block, blurRadiusY = 5F) ``` ``` fun setDropShadowClip( block: DesignBlock, clip: Boolean, ) ``` Set the drop shadow's clipping of the given design block. (Only applies to shapes.) Required scope: "appearance/shadow" * `block`: the block whose drop shadow's clip should be set. * `clip`: the drop shadow's clip to be set. ``` engine.block.setDropShadowClip(block, clip = false) ``` ``` fun getDropShadowClip(block: DesignBlock): Boolean ``` Get the drop shadow's clipping of the given design block. * `block`: the block whose drop shadow's clipping should be queried. * Returns the drop shadow's clipping. ``` val dropShadowClip = engine.block.getDropShadowClip(block) ``` ``` fun getDropShadowBlurRadiusX(block: DesignBlock): Float ``` Get the drop shadow's blur radius on the x axis of the given design block. * `block`: the block whose drop shadow's blur radius on the x axis should be queried. * Returns the blur radius. ``` val dropShadowBlurRadiusX = engine.block.getDropShadowBlurRadiusX(block) ``` ``` fun getDropShadowBlurRadiusY(block: DesignBlock): Float ``` Get the drop shadow's blur radius on the y axis of the given design block. * `block`: the block whose drop shadow's blur radius on the y axis should be queried. * Returns the blur radius. ``` val dropShadowBlurRadiusY = engine.block.getDropShadowBlurRadiusY(block) ``` Configure Inspector Bar - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/inspector-bar/?platform=android&language=kotlin # Configure Inspector Bar In this example, we will show you how to make inspector bar configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-inspector-bar). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-inspector-bar) ## Inspector Bar Architecture ![](/docs/cesdk/167235b329c090149fd0f5fda2ce9db6/inspector-bar-android.png) `InspectorBar` is a list of items placed horizontally at the bottom of the editor. It is visible when a design block is selected and its items provide different editing capabilities to the selected design block. It has type `InspectorBar : EditorComponent` and every item in the inspector bar has type `InspectorBar.Item : EditorComponent`. `InspectorBar.Item` is an abstract class that currently has two implementations: `InspectorBar.Button` and `InspectorBar.Custom`. `InspectorBar.Button` is an editor component that has an icon and a text positioned in a column, while `InspectorBar.Custom` is a fully custom editor component that allows drawing arbitrary content. Prefer using `InspectorBar.Custom` for rendering custom content in the inspector bar over inheriting from `InspectorBar.Item`. ## InspectorBar Configuration `InspectorBar` is part of the `EditorConfiguration`, therefore, in order to configure the inspector bar we need to configure the `EditorConfiguration`. Below you can find the list of available configurations of the inspector bar. To demonstrate the default values, all parameters are assigned to their default values. ``` val editorConfiguration = EditorConfiguration.rememberForDesign( inspectorBar = { InspectorBar.remember( // Implementation is too large, check the implementation of InspectorBar.defaultScope scope = InspectorBar.defaultScope, listBuilder = InspectorBar.ListBuilder.remember(), horizontalArrangement = { Arrangement.Start }, // Also available via InspectorBar.defaultItemsRowEnterTransition itemsRowEnterTransition = { remember { slideInHorizontally( animationSpec = tween(400, easing = CubicBezierEasing(0.05F, 0.7F, 0.1F, 1F)), initialOffsetX = { it / 3 }, ) } }, // Also available via InspectorBar.defaultItemsRowExitTransition itemsRowExitTransition = { ExitTransition.None }, visible = { editorContext.safeSelection != null }, // Also available via InspectorBar.defaultEnterTransition enterTransition = { remember { slideInVertically( animationSpec = tween( durationMillis = 400, easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f), ), initialOffsetY = { it }, ) } }, // Also available via InspectorBar.defaultExitTransition exitTransition = { remember { slideOutVertically( animationSpec = tween( durationMillis = 150, easing = CubicBezierEasing(0.3f, 0f, 0.8f, 0.15f), ), targetOffsetY = { it }, ) } }, // Implementation is too large, check the implementation of InspectorBar.defaultDecoration decoration = { InspectorBar.defaultDecoration }, // default value is { it() } itemDecoration = { Box(modifier = Modifier.padding(2.dp)) { it() } }, ) }, ) ``` * `scope` - the scope of this component. Every new value will trigger recomposition of all the lambda parameters below. If you need to access `EditorScope` to construct the scope, use `LocalEditorScope`. Consider using Compose `State` objects in the lambda parameters below for granular recompositions over updating the scope, since scope change triggers full recomposition of the inspector bar. Prefer updating individual `InspectorBar.Item`s over updating the whole inspector bar. Ideally, scope should be updated when the parent scope (scope of the parent component) is updated and when you want to observe changes from the `Engine`. By default the scope is updated when the parent scope (accessed via `LocalEditorScope`) is updated, when selection is changed to a different design block (or unselected) and when the parent of the currently selected design block is changed to a different design block. ``` // Implementation is too large, check the implementation of InspectorBar.defaultScope scope = InspectorBar.defaultScope, ``` * `listBuilder` - a builder that registers the list of `InspectorBar.Item`s that should be part of the inspector bar. Note that registering does not mean displaying. The items will be displayed if `InspectorBar.Item.visible` is true for them. By default `InspectorBar.ListBuilder.remember` is used. For more details, see [ListBuilder Configuration](/docs/cesdk/mobile-editor/configuration/inspector-bar/#inspectorbarlistbuilder-configuration) section. ``` listBuilder = InspectorBar.ListBuilder.remember(), ``` * `horizontalArrangement` - the horizontal arrangement that should be used to render the items in the inspector bar horizontally. Default value is `Arrangement.Start`. ``` horizontalArrangement = { Arrangement.Start }, ``` * `itemsRowEnterTransition` - separate transition of the items' row only when `enterTransition` is running. Default value is a right to left horizontal transition. ``` // Also available via InspectorBar.defaultItemsRowEnterTransition itemsRowEnterTransition = { remember { slideInHorizontally( animationSpec = tween(400, easing = CubicBezierEasing(0.05F, 0.7F, 0.1F, 1F)), initialOffsetX = { it / 3 }, ) } }, ``` * `itemsRowExitTransition` - separate transition of the items' row only when `exitTransition` is running. Default value is always no exit transition. ``` // Also available via InspectorBar.defaultItemsRowExitTransition itemsRowExitTransition = { ExitTransition.None }, ``` * `visible` - whether the inspector bar should be visible. By default the value is true when a design block is selected in the editor, false otherwise. ``` visible = { editorContext.safeSelection != null }, ``` * `enterTransition` - transition of the inspector bar when it enters the parent composable. Default value is a bottom to top vertical transition. ``` // Also available via InspectorBar.defaultEnterTransition enterTransition = { remember { slideInVertically( animationSpec = tween( durationMillis = 400, easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f), ), initialOffsetY = { it }, ) } }, ``` * `exitTransition` - transition of the inspector bar when it exits the parent composable. Default value is a top to bottom vertical transition. ``` // Also available via InspectorBar.defaultExitTransition exitTransition = { remember { slideOutVertically( animationSpec = tween( durationMillis = 150, easing = CubicBezierEasing(0.3f, 0f, 0.8f, 0.15f), ), targetOffsetY = { it }, ) } }, ``` * `decoration` - decoration of the inspector bar. Useful when you want to add custom background, foreground, shadow, paddings etc. By default decoration adds background color, applies paddings and adds a close button to the inspector bar. ``` // Implementation is too large, check the implementation of InspectorBar.defaultDecoration decoration = { InspectorBar.defaultDecoration }, ``` * `itemDecoration` - decoration of the items in the inspector bar. Useful when you want to add custom background, foreground, shadow, paddings etc to the items. Prefer using this decoration when you want to apply the same decoration to all the items, otherwise set decoration to individual items. Default value is always no decoration. ``` // default value is { it() } itemDecoration = { Box(modifier = Modifier.padding(2.dp)) { it() } }, ``` ## InspectorBar.ListBuilder Configuration There are two main ways to create an instance of `InspectorBar.ListBuilder`. First way is to call `modify` on an existing builder, and the second way is to create the builder from scratch. Currently, there is a single available builder, accessible via `InspectorBar.ListBuilder.remember`. It provides a single general order of items in the inspector bar. There is a visibility information next to each item that mentions for which design blocks/fill types the item is going to be visible. ``` @Composable fun InspectorBar.ListBuilder.remember(): ListBuilder> { return InspectorBar.ListBuilder.remember { add { InspectorBar.Button.rememberReplace() } // Video, Image, Sticker, Audio add { InspectorBar.Button.rememberEditText() } // Text add { InspectorBar.Button.rememberFormatText() } // Text add { InspectorBar.Button.rememberFillStroke() } // Page, Video, Image, Shape, Text add { InspectorBar.Button.rememberVolume() } // Video, Audio add { InspectorBar.Button.rememberCrop() } // Video, Image add { InspectorBar.Button.rememberAdjustments() } // Video, Image add { InspectorBar.Button.rememberFilter() } // Video, Image add { InspectorBar.Button.rememberEffect() } // Video, Image add { InspectorBar.Button.rememberBlur() } // Video, Image add { InspectorBar.Button.rememberShape() } // Video, Image, Shape add { InspectorBar.Button.rememberSelectGroup() } // Video, Image, Sticker, Shape, Text add { InspectorBar.Button.rememberEnterGroup() } // Group add { InspectorBar.Button.rememberLayer() } // Video, Image, Sticker, Shape, Text add { InspectorBar.Button.rememberSplit() } // Video, Image, Sticker, Shape, Text, Audio add { InspectorBar.Button.rememberMoveAsClip() } // Video, Image, Sticker, Shape, Text add { InspectorBar.Button.rememberMoveAsOverlay() } // Video, Image, Sticker, Shape, Text add { InspectorBar.Button.rememberReorder() } // Video, Image, Sticker, Shape, Text add { InspectorBar.Button.rememberDuplicate() } // Video, Image, Sticker, Shape, Text, Audio add { InspectorBar.Button.rememberDelete() } // Video, Image, Sticker, Shape, Text, Audio } } ``` ### Modifying an Existing Builder In this example, we will modify the only available builder: `InspectorBar.ListBuilder.remember`. Modifying builders can be used, when you do not want to touch the default general order of the items in the builder, but rather add additional items and replace/hide some of the default items. To achieve that, there are multiple available functions in the scope of `modify` lambda: ``` listBuilder = InspectorBar.ListBuilder.remember().modify { addFirst { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.first"), vectorIcon = null, text = { "First Button" }, onClick = {}, ) } addLast { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.last"), vectorIcon = null, text = { "Last Button" }, onClick = {}, ) } addAfter(id = InspectorBar.Button.Id.layer) { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.afterLayer"), vectorIcon = null, text = { "After Layer" }, onClick = {}, ) } addBefore(id = InspectorBar.Button.Id.crop) { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.beforeCrop"), vectorIcon = null, text = { "Before Crop" }, onClick = {}, ) } replace(id = InspectorBar.Button.Id.formatText) { // Note that it can be replaced with a component that has a different id. InspectorBar.Button.rememberFormatText( vectorIcon = { IconPack.Music }, ) } remove(id = InspectorBar.Button.Id.delete) }, ``` * `addFirst` - prepends a new `InspectorBar.Item` in the list. ``` addFirst { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.first"), vectorIcon = null, text = { "First Button" }, onClick = {}, ) } ``` * `addLast` - appends a new `InspectorBar.Item` in the list. ``` addLast { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.last"), vectorIcon = null, text = { "Last Button" }, onClick = {}, ) } ``` * `addAfter` - adds a new `InspectorBar.Item` right after the item with provided id. An exception will be thrown if no item exists with provided id in the default builder. ``` addAfter(id = InspectorBar.Button.Id.layer) { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.afterLayer"), vectorIcon = null, text = { "After Layer" }, onClick = {}, ) } ``` * `addBefore` - adds a new `InspectorBar.Item` right before the item with provided id. An exception will be thrown if no item exists with provided id in the default builder. ``` addBefore(id = InspectorBar.Button.Id.crop) { InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.beforeCrop"), vectorIcon = null, text = { "Before Crop" }, onClick = {}, ) } ``` * `replace` - replaces the `InspectorBar.Item` with provided id. An exception will be thrown if no item exists with provided id in the default builder. Also note that the new item does not need to have the same id. ``` replace(id = InspectorBar.Button.Id.formatText) { // Note that it can be replaced with a component that has a different id. InspectorBar.Button.rememberFormatText( vectorIcon = { IconPack.Music }, ) } ``` * `remove` - removes the `InspectorBar.Item` with provided id. An exception will be thrown if no item exists with provided id in the default builder. ``` remove(id = InspectorBar.Button.Id.delete) ``` #### Warning Note that the order of items may change between editor versions, therefore ListBuilder.modify must be used with care. Consider creating a new builder if you want to have strict ordering between different editor versions. ### Creating a New Builder In this example, we will create a builder from scratch that will be used in the `InspectorBar` of `DesignEditor` solution. Creating a new builder is recommended, when you do not want to use the default order of items provided by IMG.LY. This example mimics reordering the default order of items in `InspectorBar.ListBuilder.remember` builder. In addition, some items are removed and a new custom item is prepended. ``` listBuilder = InspectorBar.ListBuilder.remember { add { val buttonScope = LocalEditorScope.current.run { remember(this) { InspectorBar.ButtonScope(parentScope = this) } } InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.custom"), vectorIcon = null, text = { "Custom Button" }, onClick = {}, scope = buttonScope, ) } add { InspectorBar.Button.rememberDuplicate() } add { InspectorBar.Button.rememberDelete() } add { InspectorBar.Button.rememberAdjustments() } add { InspectorBar.Button.rememberEffect() } add { InspectorBar.Button.rememberBlur() } add { InspectorBar.Button.rememberReplace() } add { InspectorBar.Button.rememberEditText() } add { InspectorBar.Button.rememberFormatText() } add { InspectorBar.Button.rememberFillStroke() } add { InspectorBar.Button.rememberVolume() } add { InspectorBar.Button.rememberCrop() } add { InspectorBar.Button.rememberShape() } add { InspectorBar.Button.rememberLayer() } add { InspectorBar.Button.rememberSplit() } add { InspectorBar.Button.rememberMoveAsClip() } add { InspectorBar.Button.rememberMoveAsOverlay() } }, ``` ## InspectorBar.Item Configuration As mentioned in the [InspectorBar Architecture](/docs/cesdk/mobile-editor/configuration/inspector-bar/#inspector-bar-architecture) section, `InspectorBar.Item` is an `EditorComponent` and it has two subtypes: `InspectorBar.Button` and `InspectorBar.Custom`. ### InspectorBar.Button Configuration In order to create an inspector bar button, use `InspectorBar.Button.remember` composable function. Below you can find the list of available configurations when creating an `InspectorBar.Button`. To demonstrate the default values, all parameters are assigned to their default values whenever possible. ``` @Composable fun rememberInspectorBarButton(): InspectorBar.Button { return InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.newButton"), scope = LocalEditorScope.current.run { remember(this) { InspectorBar.ButtonScope(parentScope = this) } }, onClick = { editorContext.eventHandler.send(EditorEvent.Sheet.Open(SheetType.Volume())) }, // default value is null icon = { Icon( imageVector = IconPack.Music, contentDescription = null, ) }, // default value is null text = { Text( text = "Hello World", ) }, enabled = { true }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, // default value is { it() } decoration = { Surface(color = MaterialTheme.colorScheme.background) { it() } }, ) } ``` * `id` - the id of the button. Note that it is highly recommended that every unique `EditorComponent` has a unique id. Parameter does not have a default value. ``` id = EditorComponentId("my.package.inspectorBar.button.newButton"), ``` * `scope` - the scope of this component. Every new value will trigger recomposition of all the lambda parameters below. If you need to access `EditorScope` to construct the scope, use `LocalEditorScope`. Consider using Compose `State` objects in the lambda parameters below for granular recompositions over updating the scope, since scope change triggers full recomposition of the button. Ideally, scope should be updated when the parent scope (scope of the parent component `InspectorBar` - `InspectorBar.Scope`) is updated and when you want to observe changes from the `Engine`. By default the scope is updated only when the parent component scope (`InspectorBar.scope`, accessed via `LocalEditorScope`) is updated. ``` scope = LocalEditorScope.current.run { remember(this) { InspectorBar.ButtonScope(parentScope = this) } }, ``` * `onClick` - the callback that is invoked when the button is clicked. Parameter does not have a default value. ``` onClick = { editorContext.eventHandler.send(EditorEvent.Sheet.Open(SheetType.Volume())) }, ``` * `icon` - the icon content of the button. If null, it will not be rendered. Default value is null. ``` // default value is null icon = { Icon( imageVector = IconPack.Music, contentDescription = null, ) }, ``` * `text` - the text content of the button. If null, it will not be rendered. Default value is null. ``` // default value is null text = { Text( text = "Hello World", ) }, ``` * `enabled` - whether the button is enabled. Default value is always true. ``` enabled = { true }, ``` * `visible` - whether the button should be visible. Default value is always true. ``` visible = { true }, ``` * `enterTransition` - transition of the button when it enters the parent composable. Default value is always no enter transition. ``` enterTransition = { EnterTransition.None }, ``` * `exitTransition` - transition of the button when it exits the parent composable. Default value is always no exit transition. ``` exitTransition = { ExitTransition.None }, ``` * `decoration` - decoration of the button. Useful when you want to add custom background, foreground, shadow, paddings etc. Default value is always no decoration. ``` // default value is { it() } decoration = { Surface(color = MaterialTheme.colorScheme.background) { it() } }, ``` Other than the main `InspectorBar.Button.remember` function, there is one more convenience overload that has two differences: 1. `icon` is replaced with `vectorIcon` lambda, that returns `VectorIcon` instead of drawing the icon content. 2. `text` is replaced with `text` lambda, that returns `String` instead of drawing the text content. ``` @Composable fun rememberInspectorBarButtonSimpleOverload(): InspectorBar.Button { return InspectorBar.Button.remember( id = EditorComponentId("my.package.inspectorBar.button.newButton"), scope = LocalEditorScope.current.run { remember(this) { InspectorBar.ButtonScope(parentScope = this) } }, onClick = { editorContext.eventHandler.send(ShowLoading) }, vectorIcon = { IconPack.Music }, // default value is null text = { "Hello World" }, // default value is null tint = null, enabled = { true }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, decoration = { Surface(color = MaterialTheme.colorScheme.background) { it() } }, ) } ``` ### InspectorBar.Custom Configuration In order to create a custom inspector bar item, use `InspectorBar.Custom.remember` composable function. Below you can find the list of available configurations when creating a `InspectorBar.Custom` item. To demonstrate the default values, all parameters are assigned to their default values whenever possible. ``` @Composable fun rememberInspectorBarCustomItem(): InspectorBar.Item { return InspectorBar.Custom.remember( id = EditorComponentId("my.package.inspectorBar.button.newCustomItem"), scope = LocalEditorScope.current.run { remember(this) { InspectorBar.ItemScope(parentScope = this) } }, visible = { true }, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, ) { Box( modifier = Modifier .fillMaxHeight() .clickable { Toast.makeText(editorContext.activity, "Hello World Clicked!", Toast.LENGTH_SHORT).show() }, ) { Text( modifier = Modifier.align(Alignment.Center), text = "Hello World", ) } } } ``` * `id` - the id of the custom item. Note that it is highly recommended that every unique `EditorComponent` has a unique id. Parameter does not have a default value. ``` id = EditorComponentId("my.package.inspectorBar.button.newCustomItem"), ``` * `scope` - the scope of this component. Every new value will trigger recomposition of all the lambda parameters below. If you need to access `EditorScope` to construct the scope, use `LocalEditorScope`. Consider using Compose `State` objects in the lambda parameters below for granular recompositions over updating the scope, since scope change triggers full recomposition of the custom item. Ideally, scope should be updated when the parent scope (scope of the parent component `InspectorBar` - `InspectorBar.Scope`) is updated and when you want to observe changes from the `Engine`. Parameter does not have a default value. ``` scope = LocalEditorScope.current.run { remember(this) { InspectorBar.ItemScope(parentScope = this) } }, ``` * `visible` - whether the custom item should be visible. Default value is always true. ``` visible = { true }, ``` * `enterTransition` - transition of the custom item when it enters the parent composable. Default value is always no enter transition. ``` enterTransition = { EnterTransition.None }, ``` * `exitTransition` - transition of the custom item when it exits the parent composable. Default value is always no exit transition. ``` exitTransition = { ExitTransition.None }, ``` * `content` - the content of the custom item. You are responsible for drawing it, handling clicks etc. Parameter does not have a default value. ``` Box( modifier = Modifier .fillMaxHeight() .clickable { Toast.makeText(editorContext.activity, "Hello World Clicked!", Toast.LENGTH_SHORT).show() }, ) { Text( modifier = Modifier.align(Alignment.Center), text = "Hello World", ) } ``` Blur - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-blur?language=kotlin&platform=android#setup # Blur In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's blur through the `block` API. Blurs can be added to any shape or page. You can create and configure individual blurs and apply them to blocks. Blocks support at most one blur at a time. The same blur may be used for different blocks at the same time. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Blur To create a blur simply use `fun createBlur(type: BlurType): DesignBlock`. ``` fun createBlur(type: BlurType): DesignBlock ``` Create a new blur, fails if type is unknown or not a valid blur type. * `type`: the type id of the block. * Returns the handle of the newly created blur. We currently support the following blur types: * `BlurType.Uniform` * `BlurType.Linear` * `BlurType.Mirrored` * `BlurType.Radial` ``` val radialBlur = engine.block.createBlur(type = BlurType.Radial) ``` ## Configuring a Blur You can configure blurs just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setFloat(radialBlur, property = "radial/radius", value = 100F) ``` ## Functions ``` fun setBlur( block: DesignBlock, blurBlock: DesignBlock, ) ``` Connects `block`'s blur to the given `blurBlock`. Required scope: "appearance/blur" * `block`: the block to update. * `blurBlock`: a blur block. ``` engine.block.setBlur(block, blurBlock = radialBlur) ``` ``` fun setBlurEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the blur of the given design block. * `block`: the block to update. * `enabled`: the new enabled value. ``` engine.block.setBlurEnabled(block, enabled = true) ``` ``` fun isBlurEnabled(block: DesignBlock): Boolean ``` Query if blur is enabled for the given block. * `block`: the block to query. * Returns true if the blur is enabled, false otherwise. ``` val isBlurEnabled = engine.block.isBlurEnabled(block) ``` ``` fun supportsBlur(block: DesignBlock): Boolean ``` Checks whether the block supports blur. * `block`: the block to query. * Returns true if the block supports blur, false otherwise. ``` if (engine.block.supportsBlur(block)) { ``` ``` fun getBlur(block: DesignBlock): DesignBlock ``` Get the blur block of the given design block. * `block`: the block to query. * Returns the blur block. ``` val existingBlur = engine.block.getBlur(block) ``` How to Use Effects & Filters - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/using-effects?language=kotlin&platform=android#setup # How to Use Effects & Filters Some [design blocks](/docs/cesdk/engine/guides/blocks/) in CE.SDK such as pages and graphic blocks allow you to add effects to them. An effect can modify the visual output of a block's [fill](/docs/cesdk/engine/guides/using-fills/). CreativeEditor SDK supports many different types of effects, such as adjustments, LUT filters, pixelization, glow, vignette and more. Similarly to blocks, each effect instance has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-effects/UsingEffects.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-effects/UsingEffects.kt) ## Setup This example uses the headless Creative Engine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. We create a scene containing a graphic block with an image fill and want to apply effects to this image. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) engine.scene.zoomToBlock( page, paddingLeft = 40F, paddingTop = 40F, paddingRight = 40F, paddingBottom = 40F, ) val block = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(block, shape = engine.block.createShape(ShapeType.Rect)) engine.block.setPositionX(block, value = 100F) engine.block.setPositionY(block, value = 50F) engine.block.setWidth(block, value = 300F) engine.block.setHeight(block, value = 300F) engine.block.appendChild(parent = page, child = block) val fill = engine.block.createFill(FillType.Image) engine.block.setString( block = fill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_1.jpg", ) engine.block.setFill(block, fill = fill) ``` ## Accessing Effects Not all types of design blocks support effects, so you should always first call the `fun supportsEffects(block: DesignBlock): Boolean` API before accessing any of the following APIs. ``` engine.block.supportsEffects(scene) // Returns false engine.block.supportsEffects(block) // Returns true ``` ## Creating an Effect In order to add effects to our block, we first have to create a new effect instance, which we can do by calling `fun createEffect(type: EffectType): DesignBlock` and passing it the type of effect that we want. In this example, we create a pixelization and an adjustment effect. Please refer to [the API docs](/docs/cesdk/engine/api/block-effects/) for a complete list of supported effect types. ``` val pixelize = engine.block.createEffect(type = EffectType.Pixelize) val adjustments = engine.block.createEffect(type = EffectType.Adjustments) ``` ## Adding Effects Now we have two effects but the output of our scene looks exactly the same as before. That is because we still need to append these effects to the graphic design block's list of effects, which we can do by calling `fun appendEffect(block: DesignBlock, effectBlock: DesignBlock)`. We can also insert or remove effects from specific indices of a block's effect list using the `fun insertEffect(block: DesignBlock, effectBlock: DesignBlock, index: Int)` and `fun removeEffect(block: DesignBlock, index: Int)` APIs. Effects will be applied to the block in the order they are placed in the block's effects list. If the same effect appears multiple times in the list, it will also be applied multiple times. In our case, the adjustments effect will be applied to the image first, before the result of that is then pixelated. ``` engine.block.appendEffect(block, effectBlock = pixelize) engine.block.insertEffect(block, effectBlock = adjustments, index = 0) // engine.block.removeEffect(rect, index = 0) ``` ## Querying Effects Use the `fun getEffects(block: DesignBlock): List` API to query the ordered list of effect ids of a block. ``` // This will return [adjustments, pixelize] val effectsList = engine.block.getEffects(block) ``` ## Destroying Effects If we created an effect that we don't want anymore, we have to make sure to destroy it using the same `fun destroy(block: DesignBlock)` API that we also call for design blocks. Effects that are attached to a design block will be automatically destroyed when the design block is destroyed. ``` val unusedEffect = engine.block.createEffect(type = EffectType.HalfTone) engine.block.destroy(unusedEffect) ``` ## Effect Properties Just like design blocks, effects with different types have different properties that you can query and modify via the API. Use `fun findAllProperties(block: DesignBlock): List` in order to get a list of all properties of a given effect. Please refer to the [API docs](/docs/cesdk/engine/api/block-effects/) for a complete list of all available properties for each type of effect. ``` val allPixelizeProperties = engine.block.findAllProperties(pixelize) val allAdjustmentProperties = engine.block.findAllProperties(adjustments) ``` Once we know the property keys of an effect, we can use the same APIs as for design blocks in order to [modify those properties](/docs/cesdk/engine/api/block-properties/). Our adjustment effect here for example will not modify the output unless we at least change one of its adjustment properties - such as the brightness - to not be zero. ``` engine.block.setInt(pixelize, property = "pixelize/horizontalPixelSize", value = 20) engine.block.setFloat(adjustments, property = "effect/adjustments/brightness", value = 0.2F) ``` ## Disabling Effects You can temporarily disable and enable the individual effects using the `fun setEffectEnabled(effectBlock: DesignBlock, enabled: Boolean)` API. When the effects are applied to a block, all disabled effects are simply skipped. Whether an effect is currently enabled or disabled can be queried with `fun isEffectEnabled(effectBlock: DesignBlock): Boolean`. ``` engine.block.setEffectEnabled(pixelize, enabled = false) engine.block.setEffectEnabled(pixelize, !engine.block.isEffectEnabled(pixelize)) ``` Observe Events - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/events?language=kotlin&platform=android#setup # Observe Events In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to subscribe to creation, update, and destruction events of design blocks. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Subscribing to Events The event API provides a single function to subscribe to design block events. The types of events are: * `Created`: The design block was just created. * `Updated`: A property of the design block was updated. * `Destroyed`: The design block was destroyed. Note that a destroyed block will have become invalid and trying to use Block API functions on it will result in an exception. You can always use the Block API's `isValid` function to verify whether a block is valid before use. All events that occur during an engine update are batched, deduplicated, and always delivered at the very end of the engine update. Deduplication means you will receive at most one `Updated` event per block per subscription, even though there could potentially be multiple updates to a block during the engine update. To be clear, this also means the order of the event list provided to your event callback won't reflect the actual order of events within an engine update. ``` fun subscribe(blocks: List = emptyList()): Flow> ``` Subscribe to block life-cycle events * `blocks`: a list of blocks to filter events by. If the list is empty, events for every block are sent. ``` engine.event.subscribe(listOf(block)) .onEach { events -> events.forEach { event -> println("Event: ${event.type} ${event.block}") if (engine.block.isValid(event.block)) { val type = engine.block.getType(event.block) println("Block type: $type") } } } .launchIn(coroutineScope) ``` Get recorded videos from the Mobile Camera - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-camera/recordings?language=kotlin&platform=android#success # Get recorded videos from the Mobile Camera Learn how to get the recorded videos from the `CameraResult` type using the Activity Result APIs. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/camera-guides-recordings/RecordingsCameraActivity.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/camera-guides-recordings/RecordingsCameraActivity.kt) ## Success A `Recording` has a `duration` and contains a list of `Video`s. The list contains either one `Video` (for single camera recording) or two `Video`s (for dual camera recordings). Dual camera is currently only available for iOS. Each `Video` has a `uri` to the video file that is stored in `Context::getFilesDir()`. Make sure to copy the file to a permanent location if you want to access it later. ``` when (result) { is CameraResult.Record -> { for (recording in result.recordings) { Log.d(TAG, "Duration: ${recording.duration}") for (video in recording.videos) { Log.d(TAG, "Video Uri: ${video.uri}") } } } else -> { Log.d(TAG, "Unhandled result") } } ``` ### Standard Camera If the user has recorded videos, the `CameraResult.Record` case will contain a list of `Recording`s, each representing a segment of the recorded video. ``` is CameraResult.Record -> { for (recording in result.recordings) { Log.d(TAG, "Duration: ${recording.duration}") for (video in recording.videos) { Log.d(TAG, "Video Uri: ${video.uri}") } } } ``` ## Failure The result is `null` in case the user dismissed the camera at any point. ``` result ?: run { Log.d(TAG, "Camera dismissed") return@rememberLauncherForActivityResult } ``` Migrating to v1.19 - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/introduction/migration_1_19?language=kotlin&platform=android#initialization Platform Web iOS Catalyst macOS Android Language Kotlin Platform: Android Language: Kotlin Version [v1.19](/docs/cesdk/faq/changelog/#v1190) of CreativeEngineSDK and CreativeEditorSDK introduces structural changes to many of the current design blocks, making them more composable and more powerful. Along with this update, there are mandatory license changes that require attention. This comes with a number of breaking changes. This document will explain the changes and describe the steps you need to take to adapt them to your setup. ## **Initialization** The initialization of the `Engine` has changed. Now `start` is a suspending function and it requires a new parameter `license` which is the API key you received from our dashboard. There is also a new optional parameter `userId` an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. ``` engine.start(license = "", userId = "") ``` ## **DesignBlockType** This class is not an enum class anymore, but a sealed class. These are the transformations of all `DesignBlockType` types: Removed: * `DesignBlockType.IMAGE` * `DesignBlockType.VIDEO` * `DesignBlockType.STICKER` * `DesignBlockType.VECTOR_PATH` * `DesignBlockType.RECT_SHAPE` * `DesignBlockType.LINE_SHAPE` * `DesignBlockType.STAR_SHAPE` * `DesignBlockType.POLYGON_SHAPE` * `DesignBlockType.ELLIPSE_SHAPE` * `DesignBlockType.COLOR_FILL` * `DesignBlockType.IMAGE_FILL` * `DesignBlockType.VIDEO_FILL` * `DesignBlockType.LINEAR_GRADIENT_FILL` * `DesignBlockType.RADIAL_GRADIENT_FILL` * `DesignBlockType.CONICAL_GRADIENT_FILL` Renamed: * `DesignBlockType.SCENE` -> `DesignBlockType.Scene` * `DesignBlockType.STACK` -> `DesignBlockType.Stack` * `DesignBlockType.CAMERA` -> `DesignBlockType.Camera` * `DesignBlockType.PAGE` -> `DesignBlockType.Page` * `DesignBlockType.AUDIO` -> `DesignBlockType.Audio` * `DesignBlockType.TEXT` -> `DesignBlockType.Text` * `DesignBlockType.GROUP` -> `DesignBlockType.Group` Added: * `DesignBlockType.Graphic` * `DesignBlockType.Cutout` Note that `DesignBlockType.values()` can be used to get the list of all instances mentioned above. ## **Graphic Design Block** A new generic `DesignBlockType.Graphic` type has been introduced, that forms the basis of the new unified block structure. ## **Shapes** Similar to how the fill of a block is a separate object which can be attached to and replaced on a design block, we have now introduced a similar concept for the shape of a block. You use the new `createShape`, `getShape` and `setShape` APIs in order to define the shape of a design block. Only the new `DesignBlockType.Graphic` block allows to change its shape with these APIs. The new available shape types are: * `ShapeType.Rect` * `ShapeType.Line` * `ShapeType.Ellipse` * `ShapeType.Polygon` * `ShapeType.Star` * `ShapeType.VectorPath` Note that `ShapeType.values()` can be used to get the list of all instances mentioned above. The following design block types are now removed in favor of using a `DesignBlockType.Graphic` block with one of the above mentioned shape instances: * `DesignBlockType.RECT_SHAPE` * `DesignBlockType.LINE_SHAPE` * `DesignBlockType.ELLIPSE_SHAPE` * `DesignBlockType.POLYGON_SHAPE` * `DesignBlockType.STAR_SHAPE` * `DesignBlockType.VECTOR_PATH` This structural change means that the shape-specific properties (e.g. the number of sides of a polygon) are not available on the design block anymore but on the shape instances instead. You will have to add calls to `getShape` to get the instance id of the shape instance and then pass that to the property getter and setter APIs. Also, remember to change property key strings in the getter and setter calls from plural `shapes/…` to singular `shape/…` to match the new type identifiers. ## **Image and Sticker** Previously, `DesignBlockType.IMAGE` and `DesignBlockType.STICKER` were their own high-level design block types. They neither support the fill APIs nor the effects APIs. Both of these blocks are now removed in favor of using a `DesignBlockType.Graphic` block with an image fill (`FillType.Image`) and using the effects APIs instead of the legacy image block’s numerous effects properties. At its core, the sticker block has always just been an image block that is heavily limited in its capabilities. You can neither crop it, nor apply any effects to it. In order to replicate the difference as closely as possible in the new unified structure, more fine-grained scopes have been added. You can now limit the adopter’s ability to crop a block and to edit its appearance. Note that since these scopes only apply to a user of the editor with the “Adopter” role, a “Creator” user will now have all of the same editing options for both images and for blocks that used to be stickers. ## **Scopes** The following is the list of changes to the design block scopes: * (Breaking) The permission to crop a block was split from `content/replace` and `design/style` into a separate scope: `layer/crop`. * Deprecated the `design/arrange` scope and renamed `design/arrange/move` → `layer/move``design/arrange/resize` → `layer/resize``design/arrange/rotate` → `layer/rotate``design/arrange/flip` → `layer/flip` * Deprecated the `content/replace` scope. For `DesignBlockType.Text` blocks, it is replaced with the new `text/edit` scope. For other blocks it is replaced with `fill/change`. * Deprecated the `design/style` scope and replaced it with the following fine-grained scopes: `text/character`, `stroke/change`, `layer/opacity`, `layer/blendMode`, `layer/visibility`, `layer/clipping`, `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` * Introduced `fill/change`, `stroke/change`, and `shape/change` scopes that control whether the fill, stroke or shape of a block may be edited by a user with an "Adopter" role. * The deprecated scopes are automatically mapped to their new corresponding scopes by the scope APIs for now until they will be removed completely in a future update. ## **Kind** While the new unified block structure both simplifies a lot of code and makes design blocks more powerful, it also means that many of the design blocks that used to have unique type ids now all have the same generic `DesignBlockType.Graphic` type, which means that calls to the `findByType` cannot be used to filter blocks based on their legacy type ids any more. Simultaneously, there are many instances in which different blocks in the scene which might have the same type and underlying technical structure have different semantic roles in the document and should therefore be treated differently by the user interface. To solve both of these problems, we have introduced the concept of a block “kind”. This is a mutable string that can be used to tag different blocks with a semantic label. You can get the kind of a block using the `getKind` API and you can query blocks with a specific kind using the `findByKind` API. CreativeEngine provides the following default kind values: * `image` * `video` * `sticker` * `scene` * `camera` * `stack` * `page` * `audio` * `text` * `shape` * `group` Unlike the immutable design block type id, you can change the kind of a block with the new `setKind` API. It is important to remember that the underlying structure and properties of a design block are not strictly defined by its kind, since the kind, shape, fill and effects of a block can be changed independent of each other. Therefore, a user-interface should not make assumptions about available properties of a block purely based on its kind. **Note** Due to legacy reasons, blocks with the kind "sticker" will continue to not allow their contents to be cropped. This special behavior will be addressed and replaced with a more general-purpose implementation in a future update. ​ ## **Asset Definitions** The asset definitions have been updated to reflect the deprecation of legacy block type ids and the introduction of the “kind” property. In addition to the “blockType” meta property, you can now also define the `“shapeType”` ,`“fillType”` and `“kind”` of the block that should be created by the default implementation of the applyAsset function. * `“blockType”` defaults to `DesignBlockType.Graphic.key (“//ly.img.ubq/graphic”)` if left unspecified. * `“shapeType”` defaults to `ShapeType.Rect.key (“//ly.img.ubq/shape/rect”)` if left unspecified * `“fillType”` defaults to `FillType.Color.key (“//ly.img.ubq/fill/color”)` if left unspecified Video block asset definitions used to specify the `“blockType”` as `“//ly.img.ubq/fill/video“ (FillType.Video.key)`. The `“fillType”` meta asset property should now be used instead for such fill type ids. ## **Automatic Migration** CreativeEngine will always continue to support scene files that contain the now removed legacy block types. Those design blocks will be automatically replaced by the equivalent new unified block structure when the scene is loaded, which means that the types of all legacy blocks will change to `DesignBlockType.Graphic`. Note that this can mean that a block gains new capabilities that it did not have before. For example, the line shape block did not have any stroke properties, so the `hasStroke` API used to return `false`. However, after the automatic migration its `DesignBlockType.Graphic` design block replacement supports both strokes and fills, so the `hasStroke` API now returns `true` . Similarly, the image block did not support fills or effects, but the `DesignBlockType.Graphic` block does. ## **Types and API Signatures** To improve the type safety of our APIs, we have moved away from using the `DesignBlockType` enum and replaced with a set of types. Those changes have affected the following APIs: * `BlockApi.create()` * `BlockApi.createFill()` * `BlockApi.createEffect()` * `BlockApi.createBlur()` * `BlockApi.findByType()` **Note** All the functions above still support the string overload variants, however, their usage will cause lint warnings in favor of type safe overloads. ## **Code Examples** This section will show some code examples of the breaking changes and how it would look like after migrating. ``` /** Block Creation */ // Creating an Image before migration val image = engine.block.create(DesignBlockType.IMAGE) engine.block.setString( block = image, property = "image/imageFileURI", value = "https://domain.com/link-to-image.jpg" ) // Creating an Image after migration val block = engine.block.create(DesignBlockType.Graphic) val rectShape = engine.block.createShape(ShapeType.Rect) val imageFill = engine.block.createFill(FillType.Image) engine.block.setString( block = imageFill, property = "fill/image/imageFileURI", value = "https://domain.com/link-to-image.jpg" ) engine.block.setShape(block, shape = rectShape) engine.block.setFill(block, fill = imageFill) engine.block.setKind(block, kind = "image") // Creating a star shape before migration val star = engine.block.create(DesignBlockType.STAR_SHAPE) engine.block.setInt(star, property = "shapes/star/points", value = 8) // Creating a star shape after migration val block = engine.block.create(DesignBlockType.Graphic) val starShape = engine.block.createShape(ShapeType.Star) val colorFill = engine.block.createFill(FillType.Color) engine.block.setInt(block = starShape, property = "shape/star/points", value = 8) engine.block.setShape(block, shape = starShape) engine.block.setFill(block, fill = colorFill) engine.block.setKind(block, kind = "shape") // Creating a sticker before migration val sticker = engine.block.create(DesignBlockType.STICKER) engine.block.setString( block = sticker, property = "sticker/imageFileURI", value = "https://domain.com/link-to-sticker.png" ) // Creating a sticker after migration val block = engine.block.create(DesignBlockType.Graphic) val rectShape = engine.block.createShape(ShapeType.Rect) val imageFill = engine.block.createFill(FillType.Image) engine.block.setString( block = imageFill, property = "fill/image/imageFileURI", value = "https://domain.com/link-to-sticker.png" ) engine.block.setShape(block, shape = rectShape) engine.block.setFill(block, fill = imageFill) engine.block.setKind(block, kind = "sticker") /** Block Creation */ ``` ``` /** Block Exploration */ // Query all images in the scene before migration val images = engine.block.findByType(DesignBlockType.IMAGE) // Query all images in the scene after migration val images = engine.block.findByType(DesignBlockType.Graphic).filter { block -> val fill = engine.block.getFill(block) engine.block.isValid(fill) && engine.block.getType(fill) == FillType.Image.key } // Query all stickers in the scene before migration val stickers = engine.block.findByType(DesignBlockType.STICKER) // Query all stickers in the scene after migration val stickers = engine.block.findByKind("sticker") // Query all Polygon shapes in the scene before migration val polygons = engine.block.findByType(DesignBlockType.POLYGON_SHAPE) // Query all Polygon shapes in the scene after migration val polygons = engine.block.findByType(DesignBlockType.Graphic).filter { block -> val shape = engine.block.getShape(block) engine.block.isValid(shape) && engine.block.getType(shape) == ShapeType.Polygon.key } /** Block Exploration */ ``` [ Previous Migrating to v1.13 ](/docs/cesdk/introduction/migration_1_13/)[ Next Migrating to v1.32 ](/docs/cesdk/introduction/migration_1_32/) Configure Color Palette - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/color-palette?language=kotlin&platform=android#configuration # Configure Color Palette In this example, we will show you how to make color palette configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-color-palette/ColorPaletteEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-color-palette/ColorPaletteEditorSolution.kt) ## Configuration Color palette configuration is part of the `EditorConfiguration` class. Use the `EditorConfiguration.getDefault` helper function to make color palette configurations. * `colorPalette` - the color palette used for UI elements that contain predefined color options, e.g., for "Fill Color" or "Stroke Color". ``` colorPalette = remember { listOf( Color(0xFF4A67FF), Color(0xFFFFD333), Color(0xFFC41230), Color(0xFF000000), Color(0xFFFFFFFF), ) }, ``` Modify Appearance - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-appearance?language=kotlin&platform=android#setup # Modify Appearance In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks appearance through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Opacity Set the translucency of the entire block. ``` fun supportsOpacity(block: DesignBlock): Boolean ``` Query if the given block has an opacity. * `block`: the block to query. * Returns true if the block has an opacity, false otherwise. ``` engine.block.supportsOpacity(image) ``` ``` fun setOpacity( block: DesignBlock, @FloatRange(from = 0.0, to = 1.0) value: Float, ) ``` Set the opacity of the given design block. Required scope: "layer/opacity" * `block`: the block whose opacity should be set. * `value`: the opacity to be set. The valid range is 0 to 1. ``` engine.block.setOpacity(image, value = 0.5F) ``` ``` @FloatRange(from = 0.0, to = 1.0) fun getOpacity(block: DesignBlock): Float ``` Get the opacity of the given design block. * `block`: the block whose opacity should be queried. * Returns the opacity. ``` engine.block.getOpacity(image) ``` ### Blend Mode Define the blending behaviour of a block. ``` fun supportsBlendMode(block: DesignBlock): Boolean ``` Query if the given block has a blend mode. * `block`: the block to query. * Returns true if the block has a blend mode, false otherwise. ``` engine.block.supportsBlendMode(image) ``` ``` fun setBlendMode( block: DesignBlock, blendMode: BlendMode, ) ``` Set the blend mode of the given design block. Required scope: "layer/blendMode" * `block`: the block whose blend mode should be set. * `blendMode`: the blend mode to be set. ``` engine.block.getBlendMode(image) ``` ``` fun getBlendMode(block: DesignBlock): BlendMode ``` Get the blend mode of the given design block. * `block`: the block whose blend mode should be queried. * Returns the blend mode. ``` engine.block.getBlendMode(image) ``` ### Background Color Manipulate the background of a block. To understand the difference between fill and background color take the text block. The glyphs of the text itself are colored by the fill color. The rectangular background given by the bounds of the block on which the text is drawn is colored by the background color. ``` fun supportsBackgroundColor(block: DesignBlock): Boolean ``` Query if the given block has background color properties. * `block`: the block to query. * Returns true if the block has background color properties, false otherwise. ``` if (engine.block.supportsBackgroundColor(image)) { ``` ``` fun setBackgroundColor( block: DesignBlock, color: RGBAColor, ) ``` Set the background color of the given design block. Required scope: "fill/change" * `block`: the block whose background color should be set. * `color`: the color to set. ``` engine.block.setBackgroundColor(page, Color.fromRGBA(r = 1F, g = 0F, b = 0F, a = 1F) // Red ``` ``` fun getBackgroundColor(block: DesignBlock): RGBAColor ``` Get the background color of the given design block. * `block`: the block whose background color should be queried. * Returns the background color. ``` engine.block.getBackgroundColor(page) ``` ``` fun setBackgroundColorEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the background of the given design block. Required scope: "fill/change" * `block`: the block whose background should be enabled or disabled. * `enabled`: if true, the background will be enabled. ``` engine.block.setBackgroundColorEnabled(page, enabled = true) ``` ``` fun isBackgroundColorEnabled(block: DesignBlock): Boolean ``` Query if the background of the given design block is enabled. * `block`: the block whose background state should be queried. * Returns true if background is enabled, false otherwise. ``` engine.block.isBackgroundColorEnabled(page) ``` Integrate the Mobile Camera - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-camera/quickstart?language=kotlin&platform=android#adding-dependency # Integrate the Mobile Camera In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile camera in your Android app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/camera-guides-quickstart/). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/camera-guides-quickstart/) ## Adding dependency Add IMG.LY maven repository to the list of maven urls in the `settings.gradle` file. ``` maven { name "IMG.LY Artifactory" url "https://artifactory.img.ly/artifactory/maven" mavenContent { includeGroup("ly.img") } } ``` Add camera dependency in the `build.gradle` file of your application module. ``` implementation "ly.img:camera:1.43.0" ``` ## Requirements In order to use the mobile camera, your application should meet the following requirements: * `buildFeatures.compose` should be `true`, as the camera is written in Jetpack Compose. ``` buildFeatures { compose true } ``` * `composeOptions.kotlinCompilerExtensionVersion` should match the kotlin version. Use the official compatibility map in [here](https://developer.android.com/jetpack/androidx/releases/compose-kotlin). ``` composeOptions { kotlinCompilerExtensionVersion = "1.5.3" } ``` * `compose-bom` version is `2023.05.01` or higher if your project uses Jetpack Compose dependencies. Note that using lower versions may cause crashes and issues in your own compose code, as our version will override yours. In case you are not using BOM, you can find the BOM to compose library version mapping in [here](https://developer.android.com/jetpack/compose/bom/bom-mapping). ``` implementation(platform("androidx.compose:compose-bom:2023.05.01")) ``` * Kotlin version is 1.9.10 or higher. * `minSdk` is 24 (Android 7) or higher. ``` minSdk 24 ``` By default, the mobile camera supports following ABIs: `arm64-v8a`, `armeabi-v7a`, `x86_64` and `x86`. If you want to filter out some of the ABIs, use `abiFilters`. ``` ndk { abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" } ``` ## Usage This example shows the basic usage of the camera using the [Activity Result APIs](https://developer.android.com/training/basics/intents/result). In this integration example, on tapping the button, the `ActivityResultLauncher` is launched, presenting the camera `Activity`. ``` cameraLauncher.launch(cameraInput) ``` ### Initialization The camera input is initialized with `EngineConfiguration`. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. ``` val cameraInput = CaptureVideo.Input( engineConfiguration = EngineConfiguration( license = "", userId = "", ), ) ``` ### CaptureVideo ActivityResultContract Here, we register a request to start the Camera designated by the `CaptureVideo` contract. ``` val cameraLauncher = rememberLauncherForActivityResult(contract = CaptureVideo()) { result -> ``` ### Result The `CaptureVideo` contract's output is a `CameraResult?`. It is `null` when the camera is dismissed by the user and non-null when the user has recorded videos. `CameraResult` is a sealed interface and the result can be obtained by casting it appropriately. ``` result ?: run { Log.d(TAG, "Camera dismissed") return@rememberLauncherForActivityResult } when (result) { is CameraResult.Record -> { val recordedVideoUris = result.recordings.flatMap { it.videos.map { it.uri } } // Do something with the recorded videos Log.d(TAG, "Recorded videos: $recordedVideoUris") } else -> { Log.d(TAG, "Unhandled result") } } ``` That is all. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-camera/configuration/). Spot Colors - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/editor-spot-colors?language=kotlin&platform=android#setup # Spot Colors In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to manage spot colors in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun findAllSpotColors(): List ``` Queries the names of currently set spot colors previously set with \`setSpotColor\`\`. * Returns the names of set spot colors. ``` engine.editor.findAllSpotColors() // ['Red', 'Yellow'] ``` ``` fun getSpotColorRGB(name: String): RGBAColor ``` Queries the RGB representation set for a spot color. If the value of the queried spot color has not been set yet, returns the default RGB representation (of magenta). The alpha value is always 1.0. * `name`: the name of a spot color. * Returns the RGB representation of a spot color. ``` val rgbaSpotRed = engine.editor.getSpotColorRGB("Red") ``` ``` fun getSpotColorCMYK(name: String): CMYKColor ``` Queries the CMYK representation set for a spot color. If the value of the queried spot color has not been set yet, returns the default RGB representation (of magenta). * `name`: the name of a spot color. * Returns the CMYK representation of a spot color. ``` val cmykSpotRed = engine.editor.getSpotColorCMYK("Red") ``` ``` fun setSpotColor( name: String, color: RGBAColor, ) ``` Sets the RGB representation of a spot color. Use this function to both create a new spot color or update an existing spot color. Note: The alpha value is ignored. * `name`: the name of a spot color. * `color`: the RGB spot color. ``` fun setSpotColor( name: String, color: CMYKColor, ) ``` Sets the CMYK representation of a spot color. Use this function to both create a new spot color or update an existing spot color. * `name`: the name of a spot color. * `color`: the CMYK spot color. ``` engine.editor.setSpotColor("Red", Color.fromRGBA(r = 1F, g = 0F, b = 0F, a = 1F)) // Create a spot color with a CMYK color approximation. // Add a CMYK approximation to the already defined 'Red' spot color. ``` ``` fun removeSpotColor(name: String) ``` Removes a spot color from the list of set spot colors. * `name`: the name of a spot color. ``` engine.editor.removeSpotColor("Red") ``` Creating a Scene From a Blob - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-blob/?platform=android&language=kotlin # Creating a Scene From a Blob In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image provided from a blob. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob/CreateSceneFromImageBlob.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob/CreateSceneFromImageBlob.kt) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `suspend fun createFromImage(imageUri: URI, dpi: Float = 300F, pixelScaleFactor: Float = 1F): DesignBlock` and passing a URI as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. First, get hold of a `blob` by fetching an image from the web. This is just for demonstration purposes and your `blob` object may come from a different source. ``` val blobUrl = URL("https://img.ly/static/ubq_samples/sample_4.jpg") val blob = withContext(Dispatchers.IO) { val outputStream = ByteArrayOutputStream() blobUrl.openStream().use { inputStream -> outputStream.use(inputStream::copyTo) } outputStream.toByteArray() } ``` Afterward, create a temporary file and save the `Data`. Create an instance of `Uri` using the temporary file. ``` val blobFile = withContext(Dispatchers.IO) { File.createTempFile(UUID.randomUUID().toString(), ".tmp").apply { outputStream().use { it.write(blob) } } } val blobUri = Uri.fromFile(blobFile) ``` Use the object `blobUri` as a source for the initial image. ``` val scene = engine.scene.createFromImage(blobUri) ``` We can retrieve the graphic block id of this initial image using `fun findByType(blockType: DesignBlockType): List`. Note that that function returns a list. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the image fill. val block = engine.block.findByType(DesignBlockType.Graphic).first() ``` We can then manipulate and modify this block. Here we modify its opacity with `fun setOpacity(block: DesignBlock, @FloatRange(from = 0.0, to = 1.0) value: Float)`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, value = 0.5F) ``` When starting with an initial image, the scenes page dimensions match the given image, and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Integrate a Custom Asset Source - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/integrate-a-custom-asset-source?language=kotlin&platform=android#local-asset-sources # Integrate a Custom Asset Source In this example, we will show you how to integrate your custom asset sources into [CE.SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-custom-asset-source/UnsplashAssetSource.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-custom-asset-source/UnsplashAssetSource.kt) With CE.SDK you can directly add external image providers like Unsplash or your own backend. A third option we will explore in this guide is using the engine's [Asset API](/docs/cesdk/engine/api/assets/) directly. Follow along with this example while we are going to add the Unsplash library. Adding an asset source is done creating an asset source definition and adding it using `fun addSource(source: AssetSource)`. The asset source needs a unique identifier as part of an object implementing the interface of the source. All Asset API methods require the asset source's unique identifier. ``` val source = UnsplashAssetSource(unsplashBaseUrl) // INSERT YOUR UNSPLASH PROXY URL HERE engine.asset.addSource(source) ``` The most important function to implement is `suspend fun findAssets(sourceId: String, query: FindAssetsQuery): FindAssetsResult`. With this function alone you can define the complete asset source. It receives the asset query as an argument and returns results asynchronously. * The argument is the `query` and describes the slice of data the engine wants to use. This includes a query string and pagination information. * The result of this query, besides the actual asset data, returns information like the current page, the next page and the total number of assets available for this specific query. Providing a `suspend` function gives us great flexibility since we are completely agnostic of how we want to get the assets. We can use `HttpURLConnection`, local storage, cache or import a 3rd party library to return the result. ``` val list = runCatching { engine.asset.findAssets( sourceId = "ly.img.asset.source.unsplash", query = FindAssetsQuery(query = "", page = 1, perPage = 10), ) }.getOrElse { it.printStackTrace() } ``` Let us implement an Unsplash asset source. Please note that this is just for demonstration purposes only and may not be ideal if you want to integrate Unsplash in your production environment. We will create a class implementing abstract class `AssetSource`. A unique `sourceId = "ly.img.asset.source.unsplash"` is passed via the constructor. There are multiple abstract methods that we need to implement, however, `findAssets` is the most important one. ``` class UnsplashAssetSource( private val baseUrl: String, ) : AssetSource(sourceId = "ly.img.asset.source.unsplash") { ``` `findAssets` is the function that receives `query` object from the engine and is supposed to return the corresponding results. Unsplash has different API endpoints for different use cases. If we want to search we need to call a different endpoint as if we just want to display images without any search term. Therefore we need to check if the query data contains a `query` string. If `findAssets` was called with a non-empty `query` we call the `/search` endpoint via `getSearchList` function, otherwise we call `getPopularList`. As we can see in the example, we are passing the `query` object to `findAssets` method, containing the following fields: * `query.query`: The current search string from the search bar in the asset library. * `query.page`: For Unsplash specifically the requested page number starts with 1. We do not query all assets at once but by pages. As the user scrolls down more pages will be requested by calls to the `findAssets` method. * `query.perPage`: Determines how many assets we want to have included per page. This might change between calls. For instance, `perPage` can be called with a small number to display a small preview, but with a higher number e.g. if we want to show more assets in a grid view. ``` override suspend fun findAssets(query: FindAssetsQuery): FindAssetsResult { return withContext(Dispatchers.IO) { if (query.query.isNullOrEmpty()) query.getPopularList() else query.getSearchList() } } private suspend fun FindAssetsQuery.getPopularList(): FindAssetsResult { val queryParams = listOf( "order_by" to "popular", "page" to page + 1, "per_page" to perPage, ).joinToString(separator = "&") { (key, value) -> "$key=$value" } val assetsArray = getResponseAsString("$baseUrl/photos?$queryParams").let(::JSONArray) return FindAssetsResult( assets = (0 until assetsArray.length()).map { assetsArray.getJSONObject(it).toAsset() }, currentPage = page, nextPage = page + 1, total = Int.MAX_VALUE, ) } private suspend fun FindAssetsQuery.getSearchList(): FindAssetsResult { val queryParams = listOf( "query" to query, "page" to page + 1, "per_page" to perPage, ).joinToString(separator = "&") { (key, value) -> "$key=$value" } val response = getResponseAsString("$baseUrl/search/photos?$queryParams").let(::JSONObject) val assetsArray = response.getJSONArray("results") val total = response.getInt("total") val totalPages = response.getInt("total_pages") return FindAssetsResult( assets = (0 until assetsArray.length()).map { assetsArray.getJSONObject(it).toAsset() }, currentPage = page, nextPage = if (page == totalPages) -1 else page + 1, total = total, ) } ``` Once we receive the response and check for success we need to map Unsplash's result to what the asset source API needs as a result. The CE.SDK expects an object with the following properties: * `assets`: An array of assets for the current query. We will take a look at what these have to look like in the next paragraph. * `currentPage`: Return the current page that was requested. * `nextPage`: This is the next page that can be requested after the current one. Should be `-1` if there is no other page (no more assets). In this case we stop querying for more even if the user has scrolled to the bottom. * `total`: The total number of assets available for the current query. If we search for "Cat" with `perPage` set to 30, we will get 30 assets, but `total` likely will be a much higher number. ``` val response = getResponseAsString("$baseUrl/search/photos?$queryParams").let(::JSONObject) val assetsArray = response.getJSONArray("results") val total = response.getInt("total") val totalPages = response.getInt("total_pages") return FindAssetsResult( assets = (0 until assetsArray.length()).map { assetsArray.getJSONObject(it).toAsset() }, currentPage = page, nextPage = if (page == totalPages) -1 else page + 1, total = total, ) ``` Every image we get as a result of Unsplash needs to be translated into an object that is expected by the asset source API. We will describe every mandatory and optional property in the following paragraphs. ``` private fun JSONObject.toAsset() = Asset( id = getString("id"), locale = "en", label = when { !isNull("description") -> getString("description") !isNull("alt_description") -> getString("alt_description") else -> null }, tags = takeIf { has("tags") }?.let { getJSONArray("tags") }?.let { (0 until it.length()).map { index -> it.getJSONObject(index).getString("title") } }?.takeIf { it.isNotEmpty() }, meta = mapOf( "uri" to getJSONObject("urls").getString("full"), "thumbUri" to getJSONObject("urls").getString("thumb"), "blockType" to DesignBlockType.Graphic.key, "fillType" to FillType.Image.key, "shapeType" to ShapeType.Rect.key, "kind" to "image", "width" to getInt("width").toString(), "height" to getInt("height").toString(), ), context = AssetContext(sourceId = "unsplash"), credits = AssetCredits( name = getJSONObject("user").getString("name"), uri = getJSONObject("user") .takeIf { it.has("links") } ?.getJSONObject("links") ?.getString("html") ?.let { Uri.parse(it) }, ), utm = AssetUTM(source = "CE.SDK Demo", medium = "referral"), ) ``` `id`: The id of the asset (mandatory). This has to be unique for this source configuration. ``` id = getString("id"), ``` `locale` (optional): The language locale for this asset is used in `label` and `tags`. ``` locale = "en", ``` `label` (optional): The label of this asset. It could be displayed in the tooltip as well as in the credits of the asset. ``` label = when { !isNull("description") -> getString("description") !isNull("alt_description") -> getString("alt_description") else -> null }, ``` `tags` (optional): The tags of this asset. It could be displayed in the credits of the asset. ``` tags = takeIf { has("tags") }?.let { getJSONArray("tags") }?.let { (0 until it.length()).map { index -> it.getJSONObject(index).getString("title") } }?.takeIf { it.isNotEmpty() }, ``` `meta`: The meta object stores asset properties that depend on the specific asset type. ``` meta = mapOf( "uri" to getJSONObject("urls").getString("full"), "thumbUri" to getJSONObject("urls").getString("thumb"), "blockType" to DesignBlockType.Graphic.key, "fillType" to FillType.Image.key, "shapeType" to ShapeType.Rect.key, "kind" to "image", "width" to getInt("width").toString(), "height" to getInt("height").toString(), ), ``` `uri`: For an image asset this is the URL to the image file that will be used to add the image to the scene. Note that we have to use the Unsplash API to obtain a usable URL at first. ``` "uri" to getJSONObject("urls").getString("full"), ``` `thumbUri`: The URI of the asset's thumbnail. It could be used in an asset library. ``` "thumbUri" to getJSONObject("urls").getString("thumb"), ``` `blockType`: The type id of the design block that should be created when this asset is applied to the scene. If omitted, CE.SDK will try to infer the block type from an optionally provided `mimeType` property (e.g. `image/jpeg`) or by loading the asset data behind `uri` and parsing the mime type from that. However, this will cause a delay before the asset can be added to the scene, which is why it is always recommended to specify the `blockType` upfront. ``` "blockType" to DesignBlockType.Graphic.key, ``` `fillType`: The type id of the fill that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a solid color fill `//ly.img.ubq/fill/color`. ``` "fillType" to FillType.Image.key, ``` `shapeType`: The type id of the shape that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a rect shape `//ly.img.ubq/shape/rect`. ``` "shapeType" to ShapeType.Rect.key, ``` `kind`: The kind that should be set to the block when this asset is applied to the scene. If omitted, CE.SDK will default to an empty string. ``` "kind" to "image", ``` `width`: The original width of the image. `height`: The original height of the image. ``` "width" to getInt("width").toString(), "height" to getInt("height").toString(), ``` `context`: Adds contextual information to the asset. Right now, this only includes the source id of the source configuration. ``` context = AssetContext(sourceId = "unsplash"), ``` `credits` (optional): Some image providers require to display credits to the asset's artist. If set, it has to be an object with the artist's `name` and a `url` to the artist page. ``` credits = AssetCredits( name = getJSONObject("user").getString("name"), uri = getJSONObject("user") .takeIf { it.has("links") } ?.getJSONObject("links") ?.getString("html") ?.let { Uri.parse(it) }, ), ``` `utm` (optional): Some image providers require to add UTM parameters to all links to the source or the artist. If set, it contains a string to the `source` (added as `utm_source`) and the `medium` (added as `utm_medium`) ``` utm = AssetUTM(source = "CE.SDK Demo", medium = "referral"), ``` After translating the asset to match the interface from the asset source API, the array of assets for the current page can be returned. Going further with our Unsplash integration we need to handle the case when no query was provided. Unsplash requires us to call a different API endpoint (`/photos`) with slightly different parameters but the basics are the same. We need to check for success, calculate `total` and `nextPage` and translate the assets. ``` val search = runCatching { engine.asset.findAssets( sourceId = "ly.img.asset.source.unsplash", query = FindAssetsQuery(query = "banana", page = 1, perPage = 10), ) }.getOrElse { it.printStackTrace() } ``` In addition to `findAssets`, there are couple more methods that need to be implemented. We have already seen that an asset can define credits for the artist. Depending on the image provider you might need to add credits and the license for the source. In case of Unsplash, this includes a link as well as the license of all assets from this source. ``` override val credits = AssetCredits( name = "Unsplash", uri = Uri.parse("https://unsplash.com/"), ) override val license = AssetLicense( name = "Unsplash license (free)", uri = Uri.parse("https://unsplash.com/license"), ) ``` ## Local Asset Sources In many cases, you already have various finite sets of assets that you want to make available via asset sources. In order to save you the effort of having to implement custom asset query callbacks for each of these asset sources, CE.SDK also allows you to create "local" asset sources, which are managed by the engine and provide search and pagination functionalities. In order to add such a local asset source, simply call the `addLocalSource` API and choose a unique id with which you can later access the asset source. ``` engine.asset.addLocalSource( sourceId = "background-videos", supportedMimeTypes = listOf("video/mp4"), ) ``` The `fun addAsset(sourceId: String, asset: AssetDefinition)` API allows you to add new asset instances to your local asset source. The local asset source then keeps track of these assets and returns matching items as the result of asset queries. Asset queries return the assets in the same order in which they were inserted into the local asset source. Note that the `AssetDefinition` type that we pass to the `addAsset` API is slightly different than the `Asset` type which is returned by asset queries. The `AssetDefinition` for example contains all localizations of the labels and tags of the same asset whereas the `Asset` is specific to the locale property of the query. ``` val asset = AssetDefinition( id = "ocean-waves-1", label = mapOf( "en" to "relaxing ocean waves", "es" to "olas del mar relajantes", ), tags = mapOf( "en" to listOf("ocean", "waves", "soothing", "slow"), "es" to listOf("mar", "olas", "calmante", "lento"), ), meta = mapOf( "uri" to "https://example.com/ocean-waves-1.mp4", "thumbUri" to "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType" to "video/mp4", "width" to "1920", "height" to "1080", ), payload = AssetPayload(color = AssetColor.RGB(r = 0F, g = 0F, b = 1F)), ) engine.asset.addAsset(sourceId = "background-videos", asset = asset) ``` Exporting to PDF with an underlayer - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/underlayer?language=kotlin&platform=android#warning # Exporting to PDF with an underlayer When printing on a non-white medium or on a special medium like fabric or glass, printing your design over an underlayer helps achieve the desired result. An underlayer will typically be printed using a special ink and be of the exact shape of your design. When exporting to PDF, you can specify that an underlayer be automatically generated in the `ExportOptions`. An underlayer will be generated by detecting the contour of all elements on a page and inserting a new block with the shape of the detected contour. This new block will be positioned behind all existing block. After exporting, the new block will be removed. The result will be a PDF file containing an additional shape of the same shape as your design and sitting behind it. The ink to be used by the printer is specified in the `ExportOptions` with a [spot color](/docs/cesdk/engine/guides/colors/). You can also adjust the scale of the underlayer shape with a negative or positive offset, in design units. #### Warning Do not flatten the resulting PDF file or you will lose the underlayer shape which sits behind your design. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Underlayer&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Underlayer&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer) ## Setup the scene We first create a new scene with a graphic block that has a color fill. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) val block = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(block, shape = engine.block.createShape(ShapeType.Star)) engine.block.setPositionX(block, value = 350F) engine.block.setPositionY(block, value = 400F) engine.block.setWidth(block, value = 100F) engine.block.setHeight(block, value = 100F) val fill = engine.block.createFill(FillType.Color) engine.block.setFill(block, fill = fill) val rgbaBlue = Color.fromRGBA(r = 0F, g = 0F, b = 1F, a = 1F) engine.block.setColor(fill, property = "fill/color/value", value = rgbaBlue) ``` ## Add the underlayer's spot color Here we instantiate a spot color with the known name of the ink the printer should use for the underlayer. The visual color approximation is not so important, so long as the name matches what the printer expects. ``` engine.editor.setSpotColor(name = "RDG_WHITE", Color.fromRGBA(r = 0.8F, g = 0.8F, b = 0.8F)) ``` ## Exporting with an underlayer We enable the automatic generation of an underlayer on export with the option `exportPdfWithUnderlayer = true`. We specify the ink to use with `underlayerSpotColorName = 'RDG_WHITE'`. In this instance, we make the underlayer a bit smaller than our design so we specify an offset of 2 design units (e.g. millimeters) with `underlayerOffset = -2.0`. ``` val mimeType = MimeType.PDF val options = ExportOptions( exportPdfWithUnderlayer = true, underlayerSpotColorName = "RDG_WHITE", underlayerOffset = -2.0F, ) val blob = engine.block.export(scene, mimeType = mimeType, options = options) withContext(Dispatchers.IO) { File.createTempFile("underlayer_example", ".pdf").apply { outputStream().channel.write(blob) } } ``` Groups - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-groups?language=kotlin&platform=android#setup # Groups In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to group blocks through the `block` API. Groups form a cohesive unit. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Grouping Multiple blocks can be grouped together to form a cohesive unit. A group being a block, it can itself be part of a group. #### What cannot be grouped * A scene * A block that already is part of a group ``` fun isGroupable(blocks: List): Boolean ``` Confirms that a given set of blocks can be grouped together. * `blocks`: a non-empty array of block ids. * Returns whether the blocks can be grouped together. ``` if (engine.block.isGroupable(listOf(member1, member2))) { ``` ``` fun group(blocks: List): DesignBlock ``` Group blocks together. * `blocks`: a non-empty array of block ids. * Returns the block id of the created group. ``` val group = engine.block.group(listOf(member1, member2)) ``` ``` fun ungroup(block: DesignBlock) ``` Ungroups a group. * `block`: the group id from a previous call to `group`. ``` engine.block.ungroup(group) ``` ``` fun enterGroup(block: DesignBlock) ``` Changes selection from selected group to a block within that group. Nothing happens if `block` is not a group. Required scope: "editor/select" * `block`: the group id from a previous call to `group`. ``` engine.block.enterGroup(group) ``` ``` fun exitGroup(block: DesignBlock) ``` Changes selection from a group's selected block to that group. Nothing happens if `block` is not a group. Required scope: "editor/select" * `block`: a block id. ``` engine.block.exitGroup(member1) ``` How to Edit Videos - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/video?language=kotlin&platform=android#setup # How to Edit Videos In addition to static designs, CE.SDK also allows you to create and edit videos. Working with videos introduces the concept of time into the scene, which requires you to switch the scene into the `"Video"` mode. In this mode, each page in the scene has its own separate timeline within which its children can be placed. The `"playback/time"` property of each page controls the progress of time through the page. In order to add videos to your pages, you can add a block with a `FillType.video` fill. As the playback time of the page progresses, the corresponding point in time of the video fill is rendered by the block. You can also customize the video fill's trim in order to control the portion of the video that should be looped while the block is visible. `DesignBlockType.Audio` blocks can be added to the scene in order to play an audio file during playback. The `playback/timeOffset` property controls after how many seconds the audio should begin to play, while the duration property defines how long the audio should play. The same APIs can be used for other design blocks as well, such as text or graphic blocks. Finally, the whole page can be exported as a video file using the `BlockApi.exportVideo` function. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-video/Video.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-video/Video.kt) ## Setup This example uses the headless Creative Engine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ## Creating a Video Scene First, we create a scene that is set up for video editing by calling the `scene.createForVideo()` API. Then we create a page, add it to the scene and define its dimensions. This page will hold our composition. ``` val scene = engine.scene.createForVideo() val page = engine.block.create(DesignBlockType.Page) engine.block.appendChild(parent = scene, child = page) engine.block.setWidth(page, value = 1280F) engine.block.setHeight(page, value = 720F) ``` ## Setting Page Durations Next, we define the duration of the page using the `fun setDuration(block: DesignBlock, duration: Double)` API to be 20 seconds long. This will be the total duration of our exported video in the end. ``` engine.block.setDuration(page, duration = 20.0) ``` ## Adding Videos In this example, we want to show two videos, one after the other. For this, we first create two graphic blocks and assign two `'video'` fills to them. ``` val video1 = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(video1, shape = engine.block.createShape(ShapeType.Rect)) val videoFill = engine.block.createFill(FillType.Video) engine.block.setString( block = videoFill, property = "fill/video/fileURI", value = "https://cdn.img.ly/assets/demo/v1/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) engine.block.setFill(video1, fill = videoFill) val video2 = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(video1, shape = engine.block.createShape(ShapeType.Rect)) val videoFill2 = engine.block.createFill(FillType.Video) engine.block.setString( block = videoFill, property = "fill/video/fileURI", value = "https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-kampus-production-8154913.mp4", ) engine.block.setFill(video2, fill = videoFill2) ``` ## Creating a Track While we could add the two blocks directly to the page and manually set their sizes and time offsets, we can alternatively also use the `DesignBlockType.Track` block to simplify this work. A track automatically adjusts the time offsets of its children to make sure that they play one after another without any gaps, based on each child's duration. Tracks themselves cannot be selected directly by clicking on the canvas, nor do they have any visual representation. We create a `DesignBlockType.Track` block, add it to the page and add both videos in the order in which they should play as the track's children. Next, we use the `fillParent` API, which will resize all children of the track to the same dimensions as the page. The dimensions of a track are always derived from the dimensions of its children, so you should not call the `setWidth` or `setHeight` APIs on a track, but on its children instead if you can't use the `fillParent` API. ``` val track = engine.block.create(DesignBlockType.Track) engine.block.appendChild(parent = page, child = track) engine.block.appendChild(parent = track, child = video1) engine.block.appendChild(parent = track, child = video2) engine.block.fillParent(track) ``` By default, each block has a duration of 5 seconds after it is created. If we want to show it on the page for a different amount of time, we can use the `setDuration` API. Note that we can just increase the duration of the first video block to 15 seconds without having to adjust anything about the second video. The `track` takes care of that for us automatically so that the second video starts playing after 15 seconds. ``` engine.block.setDuration(video1, duration = 15.0) ``` If the video is longer than the duration of the graphic block that it's attached to, it will cut off once the duration of the graphic is reached. If it is too short, the video will automatically loop for as long as its graphic block is visible. We can also manually define the portion of our video that should loop within the page using the `fun setTrimOffset(block: DesignBlock, offset: Double)` and `fun setTrimLength(block: DesignBlock, length: Double)` APIs. We use the trim offset to cut away the first second of the video and the trim length to only play 10 seconds of the video. ``` // Make sure that the video is loaded before calling the trim APIs. engine.block.forceLoadAVResource(videoFill) engine.block.setTrimOffset(videoFill, offset = 1.0) engine.block.setTrimLength(videoFill, length = 10.0) ``` We can control if a video will loop back to its beginning by calling `fun setLooping(block: DesignBlock, looping: Boolean)`. Otherwise, the video will simply hold its last frame instead and audio will stop playing. Looping behavior is activated for all blocks by default. ``` engine.block.setLooping(videoFill, looping = true) ``` ## Audio If the video of a video fill contains an audio track, that audio will play automatically by default when the video is playing. We can mute it by calling `fun setMuted(block: DesignBlock, muted: Boolean)`. ``` engine.block.setMuted(videoFill, muted = true) ``` We can also add audio-only files to play together with the contents of the scene by adding an `audio` block to the scene and assigning it the uri of the audio file. ``` val audio = engine.block.create(DesignBlockType.Audio) engine.block.appendChild(parent = page, child = audio) engine.block.setString( block = audio, property = "audio/fileURI", value = "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a", ) ``` We can adjust the volume level of any audio block or video fill by calling `fun setVolume(block: DesignBlock, volume: Float)`. The volume is given as a fraction in the range of 0 to 1. ``` // Set the volume level to 70%. engine.block.setVolume(audio, volume = 0.7F) ``` By default, our audio block will start playing at the very beginning of the page. We can change this by specifying how many seconds into the page it should begin to play using the `fun setTimeOffset(block: DesignBlock, offset: Double)` API. ``` // Start the audio after two seconds of playback. engine.block.setTimeOffset(audio, offset = 2.0) ``` By default, our audio block will have a duration of 5 seconds. We can change this by specifying its duration in seconds by using the `fun setDuration(block: DesignBlock, duration: Double)` API. ``` // Give the Audio block a duration of 7 seconds. engine.block.setDuration(audio, duration = 7.0) ``` ## Exporting Video You can start exporting the entire page as a video file by calling `blockApi.exportVideo`. The encoding process will run in the background. You can get notified about the progress of the encoding process by using `progressCallback` parameter. Since the encoding process runs in the background, the engine will stay interactive. So, you can continue to use the engine to manipulate the scene. Please note that these changes won't be visible in the exported video file because the scene's state has been frozen at the start of the export. ``` // Export page as mp4 video. val blob = engine.block.exportVideo( block = page, timeOffset = 0.0, duration = engine.block.getDuration(page), mimeType = MimeType.MP4, progressCallback = { println( "Rendered ${it.renderedFrames} frames and encoded ${it.encodedFrames} frames out of ${it.totalFrames} frames", ) }, ) ``` Effects & Filters - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-effects?language=kotlin&platform=android#setup # Effects & Filters In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's effects through the `block` API. Effects can be added to any shape or page. You can create and configure multiple effects and apply them to blocks. Effects will be applied in the order they are appended to the block's effects list. If the same effect appears multiple times in the list it will also be applied multiple times. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating an Effect To create an effect simply use `fun createEffect(type: EffectType): DesignBlock` and pass. The available types are described [here](/docs/cesdk/engine/api/block-effect-types/) and range from LUT based color filters to advanced effects. ``` val pixelize = engine.block.createEffect(type = EffectType.Pixelize) ``` ``` fun createEffect(type: EffectType): DesignBlock ``` Create a new effect block, fails if type is unknown or not a valid effect block type. * `type`: the type id of the effect. * Returns the created effects handle. ``` val pixelize = engine.block.createEffect(type = EffectType.Pixelize) ``` We currently support the following effect types: * `EffectType.Adjustments` * `EffectType.CrossCut` * `EffectType.DotPattern` * `EffectType.DuoToneFilter` * `EffectType.ExtrudeBlur` * `EffectType.Glow` * `EffectType.GreenScreen` * `EffectType.HalfTone` * `EffectType.Linocut` * `EffectType.Liquid` * `EffectType.LutFilter` * `EffectType.Mirror` * `EffectType.Outliner` * `EffectType.Pixelize` * `EffectType.Posterize` * `EffectType.RadialPixel` * `EffectType.Recolor` * `EffectType.Sharpie` * `EffectType.Shifter` * `EffectType.TiltShift` * `EffectType.TvGlitch` * `EffectType.Vignette` ``` val pixelize = engine.block.createEffect(type = EffectType.Pixelize) ``` ## Configuring an Effect You can configure effects just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setInt(pixelize, property = "pixelize/horizontalPixelSize", value = 5F) ``` ## Adding an Effect ``` fun appendEffect( block: DesignBlock, effectBlock: DesignBlock, ) ``` Inserts an effect at the end of the list of effects. The same effect can appear multiple times in the list and won't be removed if appended again. Required scope: "appearance/effect" * `block`: the block to append the effect to. * `effectBlock`: the effect to insert. ``` engine.block.appendEffect(block, effectBlock = pixelize) ``` ``` fun supportsEffects(block: DesignBlock): Boolean ``` Queries whether the block supports effects. * `block`: the block to query. * Returns true if the block can render effects, false otherwise. ``` if (engine.block.supportsEffects(block)) { ``` ## Managing the Order of Effects ``` fun insertEffect( block: DesignBlock, effectBlock: DesignBlock, index: Int, ) ``` Inserts an effect at the given index into the list of effects of the given block. The same effect can appear multiple times in the list and won't be removed if appended again. Required scope: "appearance/effect" * `block`: the block to update. * `effectBlock`: the effect to insert. * `index`: the index at which the effect shall be inserted. ``` engine.block.insertEffect(block, effectBlock = pixelize, index = 0) ``` ``` fun removeEffect( block: DesignBlock, index: Int, ) ``` Removes the effect at the given index. Required scope: "appearance/effect" * `block`: the block to remove the effect from. * `index`: the index where the effect is stored. ``` engine.block.removeEffect(block, index = 1) ``` ``` fun getEffects(block: DesignBlock): List ``` Get a list of all effects attached to this block. * `block`: the block to query. * Returns a list of effects or an error, if the block doesn't support effects. ``` val effects = engine.block.getEffects(block) ``` ## Enabling and Disabling Effects ``` fun setEffectEnabled( effectBlock: DesignBlock, enabled: Boolean, ) ``` Sets the enabled state of an effect block. * `effectBlock`: The effect block to update. * `enabled`: the new state. ``` engine.block.setEffectEnabled(effects[0], enabled = false) ``` ``` fun isEffectEnabled(effectBlock: DesignBlock): Boolean ``` Queries whether an effect block is enabled and therefore applies its effect. * `effectBlock`: The effect block to query. * Returns true if the effect is enabled, false otherwise. ``` engine.block.isEffectEnabled(effects[0]) ``` Save Scenes to a String - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/save-scene-to-string?language=kotlin&platform=android#warning # Save Scenes to a String In this example, we will show you how to save scenes as a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-save-scene-to-string/SaveSceneToString.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-save-scene-to-string/SaveSceneToString.kt) CreativeEditor SDK allows you to save scenes in a binary format to share them between editors or store them for later editing. This is done by converting the contents of a scene to a single string, which can then be stored or transferred. #### Warning A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. To get hold of such a string, you need to use `engine.scene.saveToString()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements, but none of the actual asset data. ``` val savedSceneString = engine.scene.saveToString(scene = scene) ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ``` println(savedSceneString) ``` Lifecycle - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-lifecycle?language=kotlin&platform=android#setup # Lifecycle In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes through the `block` Api. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. Only blocks that are direct or indirect children of a `page` block are rendered. Scenes without any `page` child may not be properly displayed by the CE.SDK editor. ## Functions ``` fun create(blockType: DesignBlockType): DesignBlock ``` Create a new block. * `blockType`: the type of the block that shall be created. * Returns the created blocks handle. ``` val block = engine.block.create(DesignBlockType.Graphic) ``` By default, the following blocks are available: * `DesignBlockType.Page` * `DesignBlockType.Graphic` * `DesignBlockType.Text` * `DesignBlockType.Audio` * `DesignBlockType.Cutout` To create a scene, use [`scene.create`](/docs/cesdk/engine/guides/create-scene/) instead. ``` suspend fun saveToString( blocks: List, allowedResourceSchemes: List = listOf("bundle", "file", "http", "https"), ): String ``` Saves the given blocks to a proprietary string. If a resource uri has a scheme that is not in `allowedResourceSchemes`, an exception will be thrown. Note: All given block handles must be valid, otherwise an exception will be thrown. * `blocks`: the blocks to save. * `allowedResourceSchemes`: the list of allowed resource schemes in the scene. * Returns a string representation of the blocks. ``` val savedBlocksString = engine.block.saveToString(blocks = listOf(block)) ``` ``` suspend fun saveToArchive(blocks: List): ByteBuffer ``` Saves the given blocks to an archive. Note: All given block handles must be valid, otherwise this call returns an error. * `blocks`: the blocks to save. * Returns a string representation of the blocks. ``` val savedBlocksArchive = engine.block.saveToArchive(blocks = listOf(block)) ``` ``` suspend fun loadFromString(block: String): List ``` Loads existing blocks from the given string. The blocks are not attached by default and won't be visible until attached to a page or the scene. The UUID of the loaded blocks is replaced with a new one. * `block`: a string representing the given blocks. * Returns a list of loaded blocks. ``` val loadedBlocksString = engine.block.loadFromString(savedBlocks) ``` ``` suspend fun loadFromArchive(archiveUri: Uri): List ``` Loads existing blocks from an archive. The blocks are not attached by default and won't be visible until attached to a page or the scene. The UUID of the loaded blocks is replaced with a new one. * `archiveUri`: the uri of the blocks archive file. * Returns a list of loaded blocks. ``` val loadedBlocksArchive = engine.block.loadFromArchive(blocksUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1_blocks.zip")) ``` ``` fun getType(block: DesignBlock): String ``` Get the type of the given block, fails if the block is invalid. * `block`: the block to query. * Returns the block type. ``` val blockType = engine.block.getType(block) ``` ``` fun setName( block: DesignBlock, name: String, ) ``` Update a block's name. * `block`: the block to update. * `name`: the name to set. ``` engine.block.setName(block, name = "someName") ``` ``` fun getName(block: DesignBlock): String ``` Get a block's name. * `block:`: The block to query. * Returns The block's name. ``` val name = engine.block.getName(block) ``` ``` fun duplicate(block: DesignBlock): DesignBlock ``` Duplicates a block including its children. Required scope: "lifecycle/duplicate" * `block`: the block to duplicate. * Returns the handle of the duplicate. ``` val duplicate = engine.block.duplicate(block) ``` ``` fun destroy(block: DesignBlock) ``` Destroys a block. Required scope: "lifecycle/destroy" * `block`: the block to destroy. ``` engine.block.destroy(duplicate) ``` ``` fun isValid(block: DesignBlock): Boolean ``` Check if a block is valid. A block becomes invalid once it has been destroyed. * `block`: the block to query. * Returns true if the block is valid, false otherwise. ``` engine.block.isValid(duplicate) // false ``` Configure Callbacks - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/callbacks?language=kotlin&platform=android#configuration # Configure Callbacks In this example, we will show you how to configure the callbacks of various editor events for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Note that the bodies of all callbacks except `onUpload` are copied from the `Design Editor` default implementations. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-callbacks/CallbacksEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-callbacks/CallbacksEditorSolution.kt) ## Configuration All the callback configurations are part of the `EngineConfiguration` class. Note that all the callbacks receive `EditorEventHandler` parameter that can be used to send UI events. For more information, check [UI events](/docs/cesdk/mobile-editor/configuration/ui-events/). * `onCreate` - the callback that is invoked when the editor is created. This is the main initialization block of both the editor and engine. Normally, you should [load](/docs/cesdk/engine/guides/load-scene/) or [create](/docs/cesdk/engine/guides/create-scene/) a scene as well as prepare asset sources in this block. We recommend that you check the availability of the scene before creating/loading a new scene since a recreated scene may already exist if the callback is invoked after a process recreation. Callback does not have a default implementation, as default scenes are solution-specific, however, `EditorDefaults.onCreate` contains the default logic. By default, it loads a scene and adds all default and demo asset sources. Note that the "create" coroutine job will survive configuration changes and will be cancelled only if the editor is closed or the process is killed when in the background. ``` onCreate = { // Note that lambda is copied from EditorDefaults.onCreate coroutineScope { // In case of process recovery, engine automatically recovers the scene that is why we need to check if (editorContext.engine.scene.get() == null) { editorContext.engine.scene.load(EngineConfiguration.defaultDesignSceneUri) } launch { val baseUri = Uri.parse("https://cdn.img.ly/assets/v3") editorContext.engine.addDefaultAssetSources(baseUri = baseUri) val defaultTypeface = TypefaceProvider().provideTypeface(editorContext.engine, "Roboto") requireNotNull(defaultTypeface) editorContext.engine.asset.addSource(TextAssetSource(editorContext.engine, defaultTypeface)) } launch { editorContext.engine.addDemoAssetSources( sceneMode = editorContext.engine.scene.getMode(), withUploadAssetSources = true, baseUri = Uri.parse("https://cdn.img.ly/assets/demo/v2"), ) } coroutineContext[Job]?.invokeOnCompletion { editorContext.eventHandler.send(HideLoading) } } }, ``` * `onExport` - the callback that is invoked when the export button is tapped. You may want to call one of the [export functions](/docs/cesdk/engine/api/block-export/) in this callback. The default implementations call `BlockApi.export` or `BlockApi.exportVideo`, write the content into a temporary file and open a system dialog for sharing the exported file. Note that the "export" coroutine job will survive configuration changes and will be cancelled only if the editor is closed or the process is killed when in the background ``` onExport = { EditorDefaults.run { editorContext.eventHandler.send(ShowLoading) val blob = editorContext.engine.block.export( block = requireNotNull(editorContext.engine.scene.get()), mimeType = MimeType.PDF, ) { scene.getPages().forEach { block.setScopeEnabled(it, key = "layer/visibility", enabled = true) block.setVisible(it, visible = true) } } val tempFile = writeToTempFile(blob) editorContext.eventHandler.send(HideLoading) editorContext.eventHandler.send(ShareFileEvent(tempFile, MimeType.PDF.key)) } }, ``` * `onUpload` - the callback that is invoked after an asset is added to `UploadAssetSourceType`. When selecting an asset to upload, a default `AssetDefinition` object is constructed based on the selected asset and the callback is invoked. By default, the callback leaves the asset definition unmodified and returns the same object. However, you may want to upload the selected asset to your server before adding it to the scene. This example demonstrates how you can access the URI of the new asset, use it to upload the file to your server, and then replace the URI with the URI of your server. Note that the "upload" coroutine job will survive configuration changes and will be cancelled only if the editor is closed or the process is killed when in the background. ``` onUpload = { assetDefinition, _ -> val meta = assetDefinition.meta ?: return@remember assetDefinition val sourceUri = Uri.parse(meta["uri"]) val uploadedUri = sourceUri // todo upload the asset here and return remote uri val newMeta = meta + listOf( "uri" to uploadedUri.toString(), "thumbUri" to uploadedUri.toString(), ) assetDefinition.copy(meta = newMeta) }, ``` * `onClose` - the callback that is invoked after a tap on the navigation icon of the toolbar or on the system back button. The callback receives a boolean parameter that indicates whether editor has unsaved changes. Default implementation sends `ShowCloseConfirmationDialogEvent` event in case that parameter is `true` and closes the editor if it is `false`. Note that the "close" coroutine job will survive configuration changes and will be cancelled only if the editor is closed or the process is killed when in the background. ``` onClose = { hasUnsavedChanges -> if (hasUnsavedChanges) { editorContext.eventHandler.send(ShowCloseConfirmationDialogEvent) } else { editorContext.eventHandler.send(EditorEvent.CloseEditor()) } }, ``` * `onError` - the callback that is invoked after the editor captures an error. Default implementation sends `ShowErrorDialogEvent` event which displays a popup dialog with action button that closes the editor. Note that the "error" coroutine job will survive configuration changes and will be cancelled only if the editor is closed or the process is killed when in the background. ``` onError = { error -> editorContext.eventHandler.send(ShowErrorDialogEvent(error)) }, ``` CreativeEngine APIs - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api?language=kotlin&platform=android#accessing-the-creativeengine-apis Platform Web Node.JS iOS Catalyst macOS Android Language Kotlin Platform: Android Language: Kotlin # CreativeEngine APIs Use the CreativeEngine APIs of the CreativeEditor SDK to implement highly customized workflows The APIs of the CreativeEngine allow you to programmatically manipulate scenes inside the editor to automate workflows and customize the user experience. ## Accessing the CreativeEngine APIs You can access the CreativeEngine APIs via the `engine` object and interact with the scene seen on screen. The examples in the API Guides will use the headless CreativeEngine: ``` import android.net.Uri import android.os.Bundle import android.view.TextureView import androidx.appcompat.app.AppCompatActivity import kotlinx.coroutines.* import ly.img.engine.* class MyActivity : AppCompatActivity() { private val engine = Engine(id = "ly.img.engine.example") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) engine.start(this) val textureView = TextureView(this) setContentView(textureView) engine.bindTextureView(textureView, savedInstanceState) /* // Or any of the following engine.bindSurfaceView(SurfaceView(this), savedInstanceState) engine.bindOffscreen(100, 100, savedInstanceState) */ CoroutineScope(Dispatchers.Main).launch { // Check whether scene already exists before loading it again as it might have been restored in engine.start(this) engine.scene.get() ?: run { val sceneUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene") engine.scene.load(sceneUri) } } } override fun onDestroy() { engine.unbind() if (isFinishing) { engine.stop() } super.onDestroy() } } ``` ## API Guides **[Scene](/docs/cesdk/engine/api/scene/)** Load, create, and save scenes or control the zoom. **[Block](/docs/cesdk/engine/api/block/)** Manipulate blocks, the elements a scene is made of, in various ways. **[Editor](/docs/cesdk/engine/api/editor/)** Control settings or history and observe state changes in your engine instance. **[Asset](/docs/cesdk/engine/api/assets/)** Manage assets by creating and reading from AssetSources. **[Event](/docs/cesdk/engine/api/events/)** Subscribe to block creation, update and destruction events. **[Variable](/docs/cesdk/engine/api/variables/)** Manage the values of pre-defined variables, allowing for quick customization of things like headlines. [ Previous Export with underlayer ](/docs/cesdk/engine/guides/underlayer/)[ Next Overview ](/docs/cesdk/engine/api/scene/) Bundle Size - CE.SDK | IMG.LY Docs [android/undefined/undefined] https://img.ly/docs/cesdk/faq/bundle-size/?platform=android Platform iOS Android Platform: Android # Bundle Size Get help with any questions regarding the engine and showcases size #### What is the download size of the engine? When included in your app, the size of the engine is different depending on the architecture: * arm64-v8a ~ 14.9MB * armeabi-v7a ~ 13.7MB * x86\_64 ~ 14.9MB * x86 ~ 14.9MB This means that the download size from Play Store will be increased by the amount mentioned above. #### What is the approximate download size of the mobile editor? In order to use the [mobile editor](/docs/cesdk/mobile-editor/), you either have to use the gradle dependency, or directly copy the solutions from our [repository](https://github.com/imgly/cesdk-android-examples). No matter which approach you choose, you can expect the download size of the mobile editor to be around the size of the engine plus a few additional megabytes. The precise size may depend on the bundled assets (scene files, images, stickers), however, with the default resources you can expect it to be around **3 MB** plus the size of the engine. Note that this does not include the size of the Jetpack Compose library. Also note that this is measured without R8 optimizations. Enabling it in your project will shrink it further. For more information on R8, follow this [link](https://developer.android.com/build/shrink-code). #### Can the engine and/or mobile editor be included in my project as a dynamic feature? Yes, both can be included as a dynamic feature. If you want to include the engine or the mobile editor via dependency as a dynamic feature, create an android module, add the dependency of the engine/mobile editor to that module and make that module dynamic. Here is the [link](https://developer.android.com/guide/playcore/feature-delivery) on how to create a dynamic module and load it. If you want to include the mobile editor by copying our repository, you do not need to create an extra module. Simply declare the `:editor` module as dynamic when you copy that module to your project. [ Previous Browser Support ](/docs/cesdk/faq/browser-support/)[ Next CORS/CSP Web Security ](/docs/cesdk/faq/cors-csp-web-security/) Zoom - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/scene-zoom?language=kotlin&platform=android#setup # Zoom In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control and observe camera zoom via the `scene` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun getZoomLevel(): Float ``` Get the zoom level of the scene or for a camera in the scene. Returns the current zoom level of the scene in unit `dpx/dot`. A zoom level of 2F results in one dot in the design to be two pixels on the screen. * Returns the zoom level of the scene. ``` engine.scene.setZoomLevel(0.5F * engine.scene.getZoomLevel()) ``` ``` fun setZoomLevel(level: Float) ``` Set the zoom level of the scene, e.g., for headless versions. This only shows an effect if the zoom level is not handled/overwritten by the UI. Setting a zoom level of 2F results in one dot in the design to be two pixels on the screen. * `level`: the zoom level with unit `dpx/dot`. is shown on the screen. ``` engine.scene.setZoomLevel(level = 1F) ``` ``` suspend fun zoomToBlock( block: DesignBlock, paddingLeft: Float = 0F, paddingTop: Float = 0F, paddingRight: Float = 0F, paddingBottom: Float = 0F, ) ``` Sets the zoom and focus to show a block. Without padding, this results in a tight view on the block. It is set asynchronous to ensure that the block dimensions are known. * `block`: the block that should be focused on. * `paddingLeft`: optional padding in screen pixels to the left of the block. * `paddingTop`: optional padding in screen pixels to the top of the block. * `paddingRight`: optional padding in screen pixels to the right of the block. * `paddingBottom`: optional padding in screen pixels to the bottom of the block. ``` engine.scene.zoomToBlock( block = scene, paddingLeft = 20F, paddingTop = 20F, paddingRight = 20F, paddingBottom = 20F ) ``` ``` fun immediateZoomToBlock( block: DesignBlock, paddingLeft: Float = 0F, paddingTop: Float = 0F, paddingRight: Float = 0F, paddingBottom: Float = 0F, forceUpdate: Boolean = false, ) ``` Sets the zoom and focus to show a block. This only shows an effect if the zoom level is not handled/overwritten by the UI. Without padding, this results in a tight view on the block. It is set immediately and assumes that the block dimensions are known. The block should not be in pending state and it's layout should be up to date. * `block`: the block that should be focused on. * `paddingLeft`: optional padding in screen pixels to the left of the block. * `paddingTop`: optional padding in screen pixels to the top of the block. * `paddingRight`: optional padding in screen pixels to the right of the block. * `paddingBottom`: optional padding in screen pixels to the bottom of the block. * `forceUpdate`: If true, the implicit update is called. ``` engine.scene.immediateZoomToBlock( block = scene, paddingLeft = 20F, paddingTop = 20F, paddingRight = 20F, paddingBottom = 20F ) ``` ``` fun enableZoomAutoFit( block: DesignBlock, axis: ZoomAutoFitAxis, paddingLeft: Float = 0.0F, paddingTop: Float = 0.0F, paddingRight: Float = 0.0F, paddingBottom: Float = 0.0F, ) ``` Continually adjusts the zoom level to fit the width or height of a block's axis-aligned bounding box. This only shows an effect if the zoom level is not handled/overwritten by the UI. Without padding, this results in a tight view on the block. No more than one block per scene can have zoom auto-fit enabled. Calling `setZoomLevel` or `zoomToBlock` disables the continuous adjustment. * `block`: the block in the scene for which to enable a zoom auto-fit. * `axis`: the block axis (or axes) for which the zoom is adjusted. * `paddingLeft`: optional padding in screen pixels to the left of the block. * `paddingTop`: optional padding in screen pixels to the top of the block. * `paddingRight`: optional padding in screen pixels to the right of the block. * `paddingBottom`: optional padding in screen pixels to the bottom of the block. ``` engine.scene.enableZoomAutoFit( block = page, axis = ZoomAutoFitAxis.BOTH, paddingLeft = 20F, paddingTop = 20F, paddingRight = 20F, paddingBottom = 20F ) ``` ``` fun disableZoomAutoFit(block: DesignBlock) ``` Disables any previously set zoom auto-fit. * `block`: the scene or a block in the scene for which to disable zoom auto-fit. ``` engine.scene.disableZoomAutoFit(page) ``` ``` fun isZoomAutoFitEnabled(block: DesignBlock): Boolean ``` Queries whether zoom auto-fit is enabled for `block`. * `block`: the scene or a block in the scene for which to query if zoom auto-fit is set. * Returns true if the given block has auto-fit set or the scene contains a block for which auto-fit is set, false otherwise. ``` engine.scene.isZoomAutoFitEnabled(page) ``` ``` @UnstableEngineApi fun enableCameraPositionClamping( blocks: List, paddingLeft: Float = 0.0F, paddingTop: Float = 0.0F, paddingRight: Float = 0.0F, paddingBottom: Float = 0.0F, scaledPaddingLeft: Float = 0.0F, scaledPaddingTop: Float = 0.0F, scaledPaddingRight: Float = 0.0F, scaledPaddingBottom: Float = 0.0F, ) ``` Continually ensures the camera position to be within the width and height of the blocks axis-aligned bounding box. Without padding, this results in a tight clamp on the blocks. * `blocks`: the blocks for which the camera position is adjusted to, usually, the scene or a page. * `paddingLeft`: optional padding in screen pixels to the left of the block. * `paddingTop`: optional padding in screen pixels to the top of the block. * `paddingRight`: optional padding in screen pixels to the right of the block. * `paddingBottom`: optional padding in screen pixels to the bottom of the block. * `scaledPaddingLeft`: optional padding in screen pixels to the left of the block that scales with the zoom level until five times the initial value. * `scaledPaddingTop`: optional padding in screen pixels to the top of the block that scales with the zoom level until five times the initial value. * `scaledPaddingRight`: optional padding in screen pixels to the right of the block that scales with the zoom level until five times the initial value. * `scaledPaddingBottom`: optional padding in screen pixels to the bottom of the block that scales with the zoom level until five times the initial value. ``` engine.scene.enableCameraPositionClamping( blocks = listOf(scene), paddingLeft = 10F, paddingTop = 10F, paddingRight = 10F, paddingBottom = 10F, scaledPaddingLeft = 0F, scaledPaddingTop = 0F, scaledPaddingRight = 0F, scaledPaddingBottom = 0F ) ``` ``` @UnstableEngineApi fun disableCameraPositionClamping() ``` Disables any previously set position clamping for the current scene. ``` engine.scene.disableCameraPositionClamping() ``` ``` @UnstableEngineApi fun isCameraPositionClampingEnabled(blockOrScene: DesignBlock): Boolean ``` Queries whether position clamping is enabled for `blockOrScene`. * `blockOrScene`: the scene or a block in the scene for which to query the position clamping. * Returns true if the given block has position clamping set or the scene contains a block for which position clamping is set, false otherwise. ``` engine.scene.isCameraPositionClampingEnabled(scene) ``` ``` @UnstableEngineApi fun enableCameraZoomClamping( blocks: List, minZoomLimit: Float = -1.0F, maxZoomLimit: Float = -1.0F, paddingLeft: Float = 0.0F, paddingTop: Float = 0.0F, paddingRight: Float = 0.0F, paddingBottom: Float = 0.0F, ) ``` Continually ensures the zoom level of the camera in the active scene to be in the given range. * Note: A zoom level of 2.0 results in one pixel in the design to be two pixels on the screen. * `blocks`: the blocks for which the camera position is adjusted to, usually, the scene or a page. * `minZoomLimit`: the minimum zoom level limit when zooming out, unlimited when negative. * `maxZoomLimit`: the maximum zoom level limit when zooming in, unlimited when negative. * `paddingLeft`: optional padding in screen pixels to the left of the block. Only applied when the block is not a camera. * `paddingTop`: optional padding in screen pixels to the top of the block. Only applied when the block is not a camera. * `paddingRight`: optional padding in screen pixels to the right of the block. Only applied when the block is not a camera. * `paddingBottom`: optional padding in screen pixels to the bottom of the block. Only applied when the block is not a camera. ``` engine.scene.enableCameraZoomClamping( listOf(page), minZoomLimit = 0.125F, maxZoomLimit = 8F, paddingLeft = 0F, paddingTop = 0F, paddingRight = 0F, paddingBottom = 0F, ) ``` ``` @UnstableEngineApi fun disableCameraZoomClamping() ``` Disables previously set zoom clamping for the current scene. ``` engine.scene.disableCameraZoomClamping() ``` ``` @UnstableEngineApi fun isCameraZoomClampingEnabled(blockOrScene: DesignBlock): Boolean ``` Queries whether zoom clamping is enabled. * `blockOrScene`: the scene or a block in the scene for which to query the zoom clamping. * Returns true if the given block has zoom clamping set or the scene contains a block for which zoom clamping is set, false otherwise. ``` engine.scene.isCameraZoomClampingEnabled(scene) ``` ``` fun onZoomLevelChanged(): Flow ``` Subscribe to changes to the zoom level. * Returns flow of zoom change events. ``` engine.scene.onZoomLevelChanged() .onEach { val zoomLevel = engine.scene.getZoomLevel() println("Zoom level is now: $zoomLevel") } .launchIn(CoroutineScope(Dispatchers.Main)) ``` ## Settings See clamp camera settings in the [editor settings](/docs/cesdk/engine/api/editor-settings/#clampcamerasettings). Get started with CE.SDK Engine - CE.SDK | IMG.LY Docs [android/activity/kotlin] https://img.ly/docs/cesdk/engine/quickstart?framework=activity&language=kotlin&platform=android#requirements # Get started with CE.SDK Engine In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s engine in your Android app. The engine enables you to power your own UI and creative workflows. Whether you want to keep your current UI or design a new one from scratch, our API can do the heavy lifting for you no matter what your starting point is. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) ### Requirements Creative Engine requires Android 7 or higher (minSdk 24) and Kotlin version 1.6.0 or higher. ## Adding dependency Add img.ly maven repository to the list of maven urls in the `settings.gradle` file. ``` maven { name "IMG.LY Artifactory" url "https://artifactory.img.ly/artifactory/maven" mavenContent { includeGroup("ly.img") } } ``` Add engine dependency in the `build.gradle` file of your application module. ``` implementation "ly.img:engine:1.43.0" ``` By default, the engine supports following ABIs: `arm64-v8a`, `armeabi-v7a`, `x86_64` and `x86`. If you want to filter out some of the ABIs, use `abiFilters`. ``` ndk { abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" } ``` ## Initialization Before any usage, `application` instance of your app needs to be passed to the `Engine`. The best place to make this initialization is the `onCreate` method of your custom `Application` class. ``` Engine.init(this) ``` ## Usage The `Engine` is a class that accepts id in the constructor. Note that if an `Engine` instance already exists with such id, then the newly created instance will internally be the same, i.e. scene will be the same. Also note that the id should not contain any forward slashes. Engine's lifecycle is not tied to the lifecycle of android components such as `Activity` or `View` and therefore, must be handled manually. ``` private val engine = Engine.getInstance(id = "ly.img.engine.example") ``` In order to use the instance, call `start()`. You should provide the license key that you received from IMG.LY. Optionally, you can provide unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Optionally, you can also pass `SavedStateRegistryOwner` object (i.e. `AppCompatActivity`, `androidx.Fragment`, `LocalSavedStateRegistryOwner` in Jetpack Compose etc.) It is highly recommended that you pass this object as it can save/restore current scene of the Engine when application is terminated/recreated due to low memory (or when Activity Kill Mode is enabled). Note that it is safe to call `start()` multiple times, however, it will return `true` only the first time. Also note that any interaction with the engine has to be on the main thread. Any interaction on any other thread will cause thrown exceptions. #### Warning Other than the _userId_ we also use the [Settings.Secure.ANDROID\_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) for better data accuracy. You should include it in the _Data safety form_ of your application when uploading it to the Play Store. ``` engine.start( license = "", userId = "", savedStateRegistryOwner = this@MyActivity, ) ``` Call one of the `bind` methods to add a target onto which engine should draw. Currently, it is possible to pass `SurfaceView`, `TextureView` and `SurfaceHolder` as target canvas. Also, it is possible to draw offscreen (without any UI) by only providing size of the canvas. ``` engine.bindTextureView(textureView) /* // Or any of the following engine.bindSurfaceView(SurfaceView(this@MyActivity)) engine.bindOffscreen(100, 100) */ ``` Whenever you want the engine to stop drawing onto the target, call `unbind()`. Note that the engine holds hard reference to the target UI component and therefore, not calling `unbind` can cause memory leaks. Also note that you cannot have multiple bound targets: every `bind()` call internally calls `unbind()` on previous target. ``` engine.unbind() ``` After these setup steps, you can use our APIs to interact with the Engine. [The next couple of pages](/docs/cesdk/engine/guides/) will document the methods available. ``` // Check whether scene already exists before loading it again as it might have been restored in engine.start). engine.scene.get() ?: run { val sceneUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene") engine.scene.load(sceneUri) } ``` Finally, when you do not need to use the engine anymore, call `stop()`. This resets the engine and clears all the JVM and native allocated memory since the previous `start()` call. In order to use the engine again after stop, call `start()`. ``` if (isFinishing) { engine.stop() } ``` Creating a Scene from Scratch - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/create-scene-from-scratch/?platform=android&language=kotlin # Creating a Scene from Scratch In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) from scratch and add a star shape. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch/CreateSceneFromScratch.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch/CreateSceneFromScratch.kt) We create an empty scene via `engine.scene.create()` which sets up the default scene block with a camera attached. Afterwards, the scene can be populated by creating additional blocks and appending them to the scene. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` val scene = engine.scene.create() ``` We first add a page with `fun create(blockType: DesignBlockType): DesignBlock` specifying a `DesignBlockType.Page` and set a parent-child relationship between the scene and this page. ``` val page = engine.block.create(DesignBlockType.Page) engine.block.appendChild(parent = scene, child = page) ``` To this page, we add a graphic design block, again with `fun create(blockType: DesignBlockType): DesignBlock`. To make it more interesting, we set a star shape and a color fill to this block to give it a visual representation. Like for the page, we set the parent-child relationship between the page and the newly added block. From then on, modifications to this block are relative to the page. ``` val block = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(block = block, shape = engine.block.createShape(ShapeType.Star)) engine.block.setFill(block = block, fill = engine.block.createFill(FillType.Color)) engine.block.appendChild(parent = page, child = block) ``` This example first appends a page child to the scene as would typically be done but it is not strictly necessary and any child block can be appended directly to a scene. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). How to Use Fills - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/using-fills?language=kotlin&platform=android#setup # How to Use Fills Some [design blocks](/docs/cesdk/engine/guides/blocks/) in CE.SDK allow you to modify or replace their fill. The fill is an object that defines the contents within the shape of a block. CreativeEditor SDK supports many different types of fills, such as images, solid colors, gradients and videos. Similarly to blocks, each fill has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-fills/UsingFills.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-using-fills/UsingFills.kt) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` val scene = engine.scene.create() val page = engine.block.create(DesignBlockType.Page) engine.block.setWidth(page, value = 800F) engine.block.setHeight(page, value = 600F) engine.block.appendChild(parent = scene, child = page) engine.scene.zoomToBlock( page, paddingLeft = 40F, paddingTop = 40F, paddingRight = 40F, paddingBottom = 40F, ) val block = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(block, shape = engine.block.createShape(ShapeType.Rect)) engine.block.setWidth(block, value = 100F) engine.block.setHeight(block, value = 100F) engine.block.setFill(block, fill = engine.block.createFill(FillType.Color)) engine.block.appendChild(parent = page, child = block) ``` ## Accessing Fills Not all types of design blocks support fills, so you should always first call the `fun supportsFill(block: DesignBlock): Boolean` API before accessing any of the following APIs. ``` engine.block.supportsFill(scene) // Returns false engine.block.supportsFill(block) // Returns true ``` In order to receive the fill id of a design block, call the `fun getFill(block: DesignBlock): DesignBlock` API. You can now pass this id into other APIs in order to query more information about the fill, e.g. its type via the `fun getType(block: DesignBlock): String` API. ``` val colorFill = engine.block.getFill(block) val defaultRectFillType = engine.block.getType(colorFill) ``` ## Fill Properties Just like design blocks, fills with different types have different properties that you can query and modify via the API. Use `fun findAllProperties(block: DesignBlock): List` in order to get a list of all properties of a given fill. For the solid color fill in this example, the call would return `["fill/color/value", "type"]`. Please refer to the [API docs](/docs/cesdk/engine/api/block-fill-types/) for a complete list of all available properties for each type of fill. ``` val allFillProperties = engine.block.findAllProperties(colorFill) ``` Once we know the property keys of a fill, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `fun setColor(block: DesignBlock, property: String, value: Color)` in order to change the color of the fill to red. Once we do this, our graphic block with rect shape will be filled with solid red. ``` engine.block.setColor( block = colorFill, property = "fill/color/value", value = Color.fromRGBA(r = 1.0F, g = 0.0F, b = 0.0F, a = 1.0F), ) ``` ## Disabling Fills You can disable and enable a fill using the `fun setFillEnabled(block: DesignBlock, enabled: Boolean)` API, for example in cases where the design block should only have a stroke but no fill. Notice that you have to pass the id of the design block and not of the fill to the API. ``` engine.block.setFillEnabled(block, enabled = false) engine.block.setFillEnabled(block, enabled = !engine.block.isFillEnabled(block)) ``` ## Changing Fill Types All design blocks that support fills allow you to also exchange their current fill for any other type of fill. In order to do this, you need to first create a new fill object using `fun createFill(fillType: FillType): DesignBlock`. We currently support the following fill types: * `FillType.Color` * `FillType.LinearGradient` * `FillType.RadialGradient` * `FillType.ConicalGradient` * `FillType.Image` * `FillType.Video` * `FillType.PixelStream` ``` val imageFill = engine.block.createFill(FillType.Image) engine.block.setString( block = imageFill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_1.jpg", ) ``` In order to assign a fill to a design block, simply call `fun setFill(block: DesignBlock, fill: DesignBlock)`. Make sure to delete the previous fill of the design block first if you don't need it any more, otherwise we will have leaked it into the scene and won't be able to access it any more, because we don't know its id. Notice that we don't use the `appendChild` API here, which only works with design blocks and not fills. When a fill is attached to one design block, it will be automatically destroyed when the block itself gets destroyed. ``` engine.block.destroy(colorFill) engine.block.setFill(block, fill = imageFill) /* // The following line would also destroy imageFill engine.block.destroy(circle) */ ``` ## Duplicating Fills If we duplicate a design block with a fill that is only attached to this block, the fill will automatically be duplicated as well. In order to modify the properties of the duplicate fill, we have to query its id from the duplicate block. ``` val duplicateBlock = engine.block.duplicate(block) engine.block.setPositionX(duplicateBlock, value = 450F) val autoDuplicateFill = engine.block.getFill(duplicateBlock) engine.block.setString( block = autoDuplicateFill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_2.jpg", ) /* // We could now assign this fill to another block. val manualDuplicateFill = engine.block.duplicate(autoDuplicateFill) engine.block.destroy(manualDuplicateFill) */ ``` ## Sharing Fills It is also possible to share a single fill instance between multiple design blocks. In that case, changing the properties of the fill will apply to all of the blocks that it's attached to at once. Destroying a block with a shared fill will not destroy the fill until there are no other design blocks left that still use that fill. ``` val sharedFillBlock = engine.block.create(DesignBlockType.Graphic) engine.block.setShape(sharedFillBlock, shape = engine.block.createShape(ShapeType.Rect)) engine.block.setPositionX(sharedFillBlock, value = 350F) engine.block.setPositionY(sharedFillBlock, value = 400F) engine.block.setWidth(sharedFillBlock, value = 100F) engine.block.setHeight(sharedFillBlock, value = 100F) engine.block.appendChild(parent = page, child = sharedFillBlock) engine.block.setFill(sharedFillBlock, fill = engine.block.getFill(block)) ``` Configure Theming - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/theming?language=kotlin&platform=android#configuration # Configure Theming In this example, we will show you how to make theming configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-theming/ThemingEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-theming/ThemingEditorSolution.kt) ## Configuration Theming configuration is part of the `EditorConfiguration` class. Use the `EditorConfiguration.getDefault` helper function to make theming configurations. * `uiMode` - the UI mode of the editor. The default value is `EditorUiMode.SYSTEM`. These are the available values: * `EditorUiMode.SYSTEM` - editor will use the light color scheme if the system is in light mode and will use the dark color scheme if the system is in dark mode. * `EditorUiMode.LIGHT` - editor will always use the light color scheme. * `EditorUiMode.DARK` - editor will always use the dark color scheme. ``` uiMode = EditorUiMode.DARK, ``` Modify Properties - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-properties?language=kotlin&platform=android#setup # Modify Properties In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify block properties through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## UUID A universally unique identifier (UUID) is assigned to each block upon creation and can be queried. This is stable across save & load and may be used to reference blocks. ``` fun getUUID(block: DesignBlock): String ``` Get a block's unique identifier. * `block:`: The block to query. * Returns The block's UUID. ``` val uuid = engine.block.getUUID(block) ``` ## Reflection For every block, you can get a list of all its properties by calling `findAllProperties`. Properties specific to a block are prefixed with the block's type followed by a forward slash. There are also common properties shared between blocks which are prefixed by their respective type. A list of all properties can be found in the [Blocks Overview](/docs/cesdk/engine/api/block/). ``` val propertyNamesStar = engine.block .findAllProperties(starShape) // List [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] val propertyNamesImage = engine.block .findAllProperties(imageFill) // List [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] val propertyNamesText = engine.block .findAllProperties(text) // List [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` ``` fun findAllProperties(block: DesignBlock): List ``` Get all available properties of a block. * `block`: the block whose properties should be queried. * Returns a list of the property names. ``` val propertyNamesStar = engine.block .findAllProperties(starShape) // List [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] val propertyNamesImage = engine.block .findAllProperties(imageFill) // List [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] val propertyNamesText = engine.block .findAllProperties(text) // List [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` Given a property you can query its type using `getPropertyType`. ``` val pointsType = engine.block.getPropertyType(property = "shape/star/points") // "Int" // highlight-Engine.HorizontalTextAlignment val alignmentType = engine.block.getPropertyType(property = "text/horizontalAlignment") // "Enum" ``` ``` fun getPropertyType(property: String): PropertyType ``` Get the type of a property given its name. * `property`: the name of the property whose type should be queried. * Returns the property type. ``` val pointsType = engine.block.getPropertyType(property = "shape/star/points") // "Int" // highlight-Engine.HorizontalTextAlignment val alignmentType = engine.block.getPropertyType(property = "text/horizontalAlignment") // "Enum" ``` To get a list of possible values for an enum property call `getEnumValues(enumProperty: string): string[]`. ``` fun getEnumValues(enumProperty: String): List ``` Get all the possible values of an enum given an enum property. * `enumProperty`: the name of the property whose enum values should be queried. * Returns a list of the enum value names as string. ``` engine.block.getEnumValues(enumProperty = "text/horizontalAlignment") ``` Some properties can only be written to or only be read. To find out what is possible with a property, you can use the `isPropertyReadable` and `isPropertyWritable` methods. ``` val readable = engine.block.isPropertyReadable("shape/star/points") val writable = engine.block.isPropertyWritable("shape/star/points") ``` ``` fun isPropertyReadable(property: String): Boolean ``` Check if a property with a given name is readable. * `property`: the name of the property whose type should be queried. * Returns whether the property is readable or not. Will return false for unknown properties. ``` val readable = engine.block.isPropertyReadable("shape/star/points") ``` ``` fun isPropertyWritable(property: String): Boolean ``` Check if a property with a given name is writeable. * `property`: the name of the property whose type should be queried. * Returns whether the property is writeable or not. Will return false for unknown properties. ``` val writable = engine.block.isPropertyWritable("shape/star/points") ``` ## Generic Properties There are dedicated setter and getter functions for each property type. You have to provide a block and the property path. Use `findAllProperties` to get a list of all the available properties a block has. Please make sure you call the setter and getter function matching the type of the property you want to set or query or else you will get an error. Use `getType` to figure out the pair of functions you need to use. ``` fun findAllProperties(block: DesignBlock): List ``` Get all available properties of a block. * `block`: the block whose properties should be queried. * Returns a list of the property names. ``` val propertyNamesStar = engine.block .findAllProperties(starShape) // List [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] val propertyNamesImage = engine.block .findAllProperties(imageFill) // List [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] val propertyNamesText = engine.block .findAllProperties(text) // List [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` ``` fun setBoolean( block: DesignBlock, property: String, value: Boolean, ) ``` Set a boolean property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setBoolean(scene, property = "scene/aspectRatioLock", value = false) ``` ``` fun getBoolean( block: DesignBlock, property: String, ): Boolean ``` Get the value of a boolean property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` engine.block.getBoolean(scene, property = "scene/aspectRatioLock") ``` ``` fun setInt( block: DesignBlock, property: String, value: Int, ) ``` Set an int property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setInt(star, property = "shape/star/points", value = points + 2) ``` ``` fun getInt( block: DesignBlock, property: String, ): Int ``` Get the value of an int property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` val points = engine.block.getInt(star, property = "shape/star/points") ``` ``` fun setFloat( block: DesignBlock, property: String, value: Float, ) ``` Set a float property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setFloat(star, property = "shape/star/innerDiameter", value = 0.75F) ``` ``` fun getFloat( block: DesignBlock, property: String, ): Float ``` Get the value of a float property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` engine.block.getFloat(star, property = "shape/star/innerDiameter") ``` ``` fun setDouble( block: DesignBlock, property: String, value: Double, ) ``` Set a double property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` val audio = engine.block.create(DesignBlockType.Audio) engine.block.appendChild(scene, audio) engine.block.setDouble(audio, property = "playback/duration", value = 1.0) ``` ``` fun getDouble( block: DesignBlock, property: String, ): Double ``` Get the value of a double property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` engine.block.getDouble(audio, property = "playback/duration") ``` ``` fun setString( block: DesignBlock, property: String, value: String, ) ``` Set a string property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setString(text, property = "text/text", value = "*o*") engine.block.setString( imageFill, property = "fill/image/imageFileURI", value = "https://img.ly/static/ubq_samples/sample_4.jpg" ) ``` ``` fun getString( block: DesignBlock, property: String, ): String ``` Get the value of a string property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` engine.block.getString(text, property = "text/text") engine.block.getString(imageFill, property = "fill/image/imageFileURI") ``` ``` fun setColor( block: DesignBlock, property: String, value: Color, ) ``` Set a color property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setColor( colorFill, property = "fill/color/value", value = Color.fromString("#FFFFFFFF") ) // White ``` ``` fun getColor( block: DesignBlock, property: String, ): Color ``` Get the value of a color property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to set. * Returns the value of the property. ``` engine.block.getColor(colorFill, property = "fill/color/value") ``` ``` fun setEnum( block: DesignBlock, property: String, value: String, ) ``` Set an enum property of the given design block to the given value. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `value`: the value to set. ``` engine.block.setEnum(text, property = "text/horizontalAlignment", value = "Center") // highlight-Engine.VerticalTextAlignment engine.block.setEnum(text, property = "text/verticalAlignment", value = "Center") ``` ``` fun getEnum( block: DesignBlock, property: String, ): String ``` Get the value of an enum property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to get. * Returns the value of the property. ``` engine.block.getEnum(text, property = "text/horizontalAlignment") engine.block.getEnum(text, property = "text/verticalAlignment") ``` ``` fun setGradientColorStops( block: DesignBlock, property: String, colorStops: List, ) ``` Set a gradient color stops property of the given design block. * `block`: the block whose property should be set. * `property`: the name of the property to set. * `colorStops`: the list of color stops. ``` engine.block.setGradientColorStops( block = gradientFill, property = "fill/gradient/colors", colorStops = listOf( GradientColorStop(stop = 0F, color = Color.fromRGBA(r = 0.1F, g = 0.2F, b = 0.3F, a = 0.4F)), GradientColorStop(stop = 1F, color = Color.fromSpotColor("test")) ) ) ``` ``` fun getGradientColorStops( block: DesignBlock, property: String, ): List ``` Get the gradient color stops property of the given design block. * `block`: the block whose property should be queried. * `property`: the name of the property to query. * Returns the list of gradient color stops. ``` engine.block.getGradientColorStops(block = gradientFill, property = "fill/gradient/colors") ``` ``` fun setSourceSet( block: DesignBlock, property: String, sourceSet: List, ) ``` Set the source set of a source set property of the given block. * `block`: the block whose source set should be set. * `property`: the name of the property to set. * `sourceSet`: the new source set. ``` val imageFill = engine.block.createFill(FillType.Image) engine.block.setSourceSet( block = imageFill, property = "fill/image/sourceSet", sourceSet = listOf( Source( uri = Uri.parse("http://img.ly/my-image.png"), width = 800, height = 600 ) ) ) ``` ``` fun getSourceSet( block: DesignBlock, property: String, ): List ``` Returns the source set of a source set property of the given block. * `block`: the block whose property should be queried. * `property`: the name of the property to get. * Returns the source set of the given block. ``` engine.block.getSourceSet(block = imageFill, property = "fill/image/sourceSet") ``` ``` suspend fun addImageFileUriToSourceSet( block: DesignBlock, property: String, uri: String, ) ``` Add a source to the `sourceSet` property of the given block. If there already exists in source set an image with the same width, that existing image will be replaced. * `block`: the block to update. * `property`: the name of the property to modify. * `uri`: the source to add to the source set. ``` engine.block.addImageFileUriToSourceSet(block = imageFill, property = "fill/image/sourceSet", uri = "https://img.ly/static/ubq_samples/sample_1.jpg"); ``` ``` suspend fun addVideoFileUriToSourceSet( block: DesignBlock, property: String, uri: String, ) ``` Add a source to the `sourceSet` property of the given block. If there already exists in source set a video with the same width, that existing video will be replaced. * `block`: the block to update. * `property`: the name of the property to modify. * `uri`: the source to add to the source set. ``` val videoFill = engine.block.createFill(FillType.Video) engine.block.addVideoFileUriToSourceSet(block = videoFill, property = "fill/video/sourceSet", uri = "https://img.ly/static/example-assets/sourceset/1x.mp4"); ``` Creating a Scene From an Initial Video URL - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/create-scene-from-video-url/?platform=android&language=kotlin # Creating a Scene From an Initial Video URL In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial video. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-video-url/CreateSceneFromVideoURL.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-create-scene-from-video-url/CreateSceneFromVideoURL.kt) Starting from an existing video allows you to use the editor for customizing individual assets. This is done by using `suspend fun createFromVideo(videoUri: URI): DesignBlock` and passing a URI as argument. Create an instance of `Uri` using the remote url. Use the object `videoRemoteUri` as a source for the initial video. ``` val videoRemoteUri = Uri.parse("https://img.ly/static/ubq_video_samples/bbb.mp4") val scene = engine.scene.createFromVideo(videoRemoteUri) ``` We can retrieve the graphic block id of this initial video using `fun findByType(blockType: DesignBlockType): List`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the video fill. val block = engine.block.findByType(DesignBlockType.Graphic).first() ``` We can then manipulate and modify this block. Here we modify its opacity with `fun setOpacity(block: DesignBlock, @FloatRange(from = 0.0, to = 1.0) value: Float)`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, value = 0.5F) ``` When starting with an initial video, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Modify Crop - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-crop?language=kotlin&platform=android#setup # Modify Crop In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks crop through the `block` Api. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Crop Manipulate the cropping region of a block by setting its scale, rotation, and translation. ``` fun supportsCrop(block: DesignBlock): Boolean ``` Query if the given block has crop properties. * `block`: the block to query. * Returns true if the block has crop properties, false otherwise. ``` engine.block.supportsCrop(image) ``` ``` fun setCropScaleX( block: DesignBlock, scaleX: Float, ) ``` Set the crop scale in x direction of the given design block. Required scope: "layer/crop" * `block`: the block whose crop scale in x direction should be set. * `scaleX`: the crop scale in x direction. ``` engine.block.setCropScaleX(image, scaleX = 2F) ``` ``` fun setCropScaleY( block: DesignBlock, scaleY: Float, ) ``` Set the crop scale in y direction of the given design block. Required scope: "layer/crop" * `block`: the block whose crop scale in y direction should be set. * `scaleY`: the crop scale in y direction. ``` engine.block.setCropScaleY(image, scaleY = 1.5F) ``` ``` fun setCropScaleRatio( block: DesignBlock, scaleRatio: Float, ) ``` Set the crop scale ratio of the given design block. This will uniformly scale the content up or down. The center of the scale operation is the center of the crop frame. Required scope: "layer/crop" * `block`: the block whose crop scale ratio should be set. * `scaleRatio`: the rotation in radians. ``` engine.block.setCropScaleRatio(image, scaleRatio = 3F) ``` ``` fun setCropRotation( block: DesignBlock, rotation: Float, ) ``` Set the crop rotation of the given design block. Required scope: "layer/crop" * `block`: the block whose crop rotation should be set. * `rotation`: the rotation in radians. ``` engine.block.setCropRotation(image, rotation = PI.toFloat()) ``` ``` fun setCropTranslationX( block: DesignBlock, translationX: Float, ) ``` Set the crop translation in x direction of the given design block. Required scope: "layer/crop" * `block`: the block whose crop translation in x direction should be set. * `translationX`: the translation in x direction. ``` engine.block.setCropTranslationX(image, translationX = -1F) ``` ``` fun setCropTranslationY( block: DesignBlock, translationY: Float, ) ``` Set the crop translation in y direction of the given design block. Required scope: "layer/crop" * `block`: the block whose crop translation in y direction should be set. * `translationY`: the translation in y direction. ``` engine.block.setCropTranslationY(image, translationY = 1F) ``` ``` fun adjustCropToFillFrame( block: DesignBlock, minScaleRatio: Float, ) ``` Adjust the crop position/scale to at least fill the crop frame. Required scope: "layer/crop" * `block`: the block to query. * `minScaleRatio`: the minimal crop scale ratio to go down to. ``` engine.block.adjustCropToFillFrame(image, minScaleRatio = 1F) ``` ``` fun setContentFillMode( block: DesignBlock, mode: ContentFillMode, ) ``` Set a block's content fill mode. Required scope: "layer/crop" * `block`: the block to update. * `mode`: the content fill mode. ``` engine.block.setContentFillMode(image, mode = ContentFillMode.CONTAIN) ``` ``` fun resetCrop(block: DesignBlock) ``` Resets the manually set crop of the given design block. The block's content fill mode is set to `ContentFillMode.COVER`. If the block has a fill, the crop values are updated so that it covers the block. Required scope: "layer/crop" * `block`: the block whose crop should be reset. ``` engine.block.resetCrop(image) ``` ``` fun getCropScaleX(block: DesignBlock): Float ``` Get the crop scale in x direction of the given design block. * `block`: the block whose crop scale in x direction should be queried. * Returns the crop scale in x direction. ``` engine.block.getCropScaleX(image) ``` ``` fun getCropScaleY(block: DesignBlock): Float ``` Get the crop scale in y direction of the given design block. * `block`: the block whose crop scale in y direction should be queried. * Returns the crop scale in y direction. ``` engine.block.getCropScaleY(image) ``` ``` fun flipCropHorizontal(block: DesignBlock) ``` Adjusts the crop in order to flip the content along its own horizontal axis. * `block`: the block whose crop should be updated. ``` engine.block.flipCropHorizontal(image) ``` ``` fun flipCropVertical(block: DesignBlock) ``` Adjusts the crop in order to flip the content along its own vertical axis. * `block`: the block whose crop should be updated. ``` engine.block.flipCropVertical(image) ``` ``` fun getCropScaleRatio(block: DesignBlock): Float ``` Get the crop scale ratio of the given design block. * `block`: the block whose crop scale ratio should be queried. * Returns the crop scale ratio. ``` engine.block.getCropScaleRatio(image) ``` ``` fun getCropRotation(block: DesignBlock): Float ``` Get the crop rotation of the given design block. * `block`: the block whose crop scale rotation should be queried. * Returns the crop rotation. ``` engine.block.getCropRotation(image) ``` ``` fun getCropTranslationX(block: DesignBlock): Float ``` Get the crop translation in x direction of the given design block. * `block`: the block whose crop translation in x direction should be queried. * Returns the crop translation in x direction. ``` engine.block.getCropTranslationX(image) ``` ``` fun getCropTranslationY(block: DesignBlock): Float ``` Get the crop translation in y direction of the given design block. * `block`: the block whose crop translation in y direction should be queried. * Returns the crop translation in y direction. ``` engine.block.getCropTranslationY(image) ``` ``` fun supportsContentFillMode(block: DesignBlock): Boolean ``` Query if the given block has a content fill mode. * `block`: the block to query. * Returns true if the block has a content fill mode, false otherwise. ``` engine.block.supportsContentFillMode(image) ``` ``` fun getContentFillMode(block: DesignBlock): ContentFillMode ``` Query a block's content fill mode. * `block`: the block to query. * Returns the current mode. ``` engine.block.getContentFillMode(image) ``` Load Scenes from a String - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/load-scene-from-string?language=kotlin&platform=android#warning # Load Scenes from a String In this example, we will show you how to load scenes from a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-load-scene-from-string/LoadSceneFromString.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-load-scene-from-string/LoadSceneFromString.kt) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by passing a string that describes a scene to the `engine.scene.loadFromString()` function. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. In this example, we fetch a scene from a remote url and load it as a string. This string could also come from the result of `suspend fun saveToString(): String`. ``` val sceneUrl = URL("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene") val sceneBlob = withContext(Dispatchers.IO) { val outputStream = ByteArrayOutputStream() sceneUrl.openStream().use { inputStream -> outputStream.use(inputStream::copyTo) } outputStream.toByteArray() } val blobString = String(sceneBlob, Charsets.UTF_8) ``` We can then pass that string to the `suspend fun load(scene: String): DesignBlock` function. The editor will then reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ``` val scene = engine.scene.load(scene = blobString) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` val text = engine.block.findByType(DesignBlockType.Text).first() engine.block.setDropShadowEnabled(text, enabled = true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Metadata - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-metadata?language=kotlin&platform=android#setup # Metadata In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine `block` API to modify a block's metadata. Metadata are a flat map of strings, that you can freely modify. They're serialized with the scene and may be used for various applications. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun hasMetadata( block: DesignBlock, key: String, ): Boolean ``` Check if the block has metadata associated with the key. * `block`: the block whose metadata will be accessed. * `key`: the key used to identify the desired piece of metadata. * Returns whether the key exists. ``` // This will return true engine.block.hasMetadata(scene, key = "author") ``` ``` fun setMetadata( block: DesignBlock, key: String, value: String, ) ``` Set a metadata value of a block identified by a key. If the key does not exist, yet, it will be added. * `block`: the block whose metadata will be accessed. * `key`: the key used to identify the desired piece of metadata. * `value`: the value to set. ``` engine.block.setMetadata(scene, key = "author", value = "img.ly") engine.block.setMetadata(image, key = "customer_id", value = "1234567890") engine.block.setMetadata(image, key = "customer_name", value = "Name") ``` ``` fun getMetadata( block: DesignBlock, key: String, ): String ``` Get a metadata value of a block identified by a key. If the key does not exist, yet, this method will fail. * `block`: the block whose metadata will be accessed. * `key`: the key used to identify the desired piece of metadata. * Returns the value associated with the key. ``` // This will return "img.ly" engine.block.BlockApi.getMetadata(scene, key = "author") // This will return "1000000" engine.block.getMetadata(image, key = "customer_id") ``` ``` fun findAllMetadata(block: DesignBlock): List ``` Query all metadata keys that exist on this block. * `block`: the block whose metadata will be accessed. * Returns a list of all metadata keys on this block. ``` // This will return ["customer_id", "customer_name"] engine.block.findAllMetadata(image) ``` ``` fun removeMetadata( block: DesignBlock, key: String, ) ``` Remove metadata associated with the key from the given block. * `block`: the block whose metadata will be accessed. * `key`: the key used to identify the desired piece of metadata. ``` engine.block.removeMetadata(image, key = "customer_id") ``` Configure the Mobile Camera - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-camera/configuration?language=kotlin&platform=android#engineconfiguration # Configure the Mobile Camera In this example, we will show you how to configure the camera. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/camera-guides-configuration/ConfiguredCameraActivity.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/camera-guides-configuration/ConfiguredCameraActivity.kt) ## EngineConfiguration All the basic engine configuration settings are part of the `EngineConfiguration` which are required to initialize the camera. ``` engineConfiguration = EngineConfiguration( license = "", userId = "", ), ``` * `license` – the license to activate the [Engine](/docs/cesdk/engine/quickstart/) with. ``` license = "", ``` * `userID` – an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. The default value is `null`. ``` userId = "", ``` ## CameraConfiguration You can optionally pass a `CameraConfiguration` object to the `CaptureVideo.Input` constructor to customise the camera experience and behaviour. ``` cameraConfiguration = CameraConfiguration( recordingColor = Color.Blue, maxTotalDuration = 30.seconds, allowExceedingMaxDuration = false, ), ``` * `recordingColor` – the color of the record button. ``` recordingColor = Color.Blue, ``` * `maxTotalDuration` – the total duration that the camera is allowed to record. ``` maxTotalDuration = 30.seconds, ``` * `allowExceedingMaxDuration` – Set to `true` to allow exceeding the `maxTotalDuration`. ``` allowExceedingMaxDuration = false, ``` How to Use Animations - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/using-animations/?platform=android&language=kotlin Platform Web iOS Catalyst macOS Android Language Kotlin Platform: Android Language: Kotlin [ Previous Using Shapes ](/docs/cesdk/engine/guides/using-shapes/)[ Next Managing Colors ](/docs/cesdk/engine/guides/colors/) How to Manage Scopes - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/scopes?language=kotlin&platform=android#setup # How to Manage Scopes CE.SDK allows you to control which parts of a block can be manipulated. Scopes describe different aspects of a block, e.g. layout or style and can be enabled or disabled for every single block. There's also the option to control a scope globally. When configuring a scope globally you can set an override to always allow or deny a certain type of manipulation for every block. Or you can configure the global scope to defer to the individual block scopes. Initially, the block-level scopes are all disabled while at the global level all scopes are set to `"Allow"`. This overrides the block-level and allows for any kind of manipulation. If you want to implement a limited editing mode in your software you can set the desired scopes on the blocks you want the user to manipulate and then restrict the available actions by globally setting the scopes to `"Defer"`. In the same way you can prevent any manipulation of properties covered by a scope by setting the respective global scope to `"Deny"`. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-scopes/Scopes.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-scopes/Scopes.kt) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` engine.scene.createFromImage(Uri.parse("https://img.ly/static/ubq_samples/imgly_logo.jpg")) val block = engine.block.findByType(DesignBlockType.Graphic).first() ``` ## Available Scopes You can retrieve all available scopes by calling `engine.editor.findAllScopes()`. ``` val scopes = engine.editor.findAllScopes() ``` We currently support the following scopes: Scope Explanation Scope `"layer/move"` Explanation Whether the block's position can be changed Scope `"layer/resize"` Explanation Whether the block can be resized Scope `"layer/rotate"` Explanation Whether the block's rotation can be changed Scope `"layer/flip"` Explanation Whether the block can be flipped Scope `"layer/crop"` Explanation Whether the block's content can be cropped Scope `"layer/clipping"` Explanation Whether the block's clipping can be changed Scope `"layer/opacity"` Explanation Whether the block's opacity can be changed Scope `"layer/blendMode"` Explanation Whether the block's blend mode can be changed Scope `"layer/visibility"` Explanation Whether the block's visibility can be changed Scope `"appearance/adjustments"` Explanation Whether the block's adjustments can be changed Scope `"appearance/filter"` Explanation Whether the block's filter can be changed Scope `"appearance/effect"` Explanation Whether the block's effect can be changed Scope `"appearance/blur"` Explanation Whether the block's blur can be changed Scope `"appearance/shadow"` Explanation Whether the block's shadow can be changed Scope `"lifecycle/destroy"` Explanation Whether the block can be deleted Scope `"lifecycle/duplicate"` Explanation Whether the block can be duplicated Scope `"editor/add"` Explanation Whether new blocks can be added Scope `"editor/select"` Explanation Whether a block can be selected or not Scope `"fill/change"` Explanation Whether the block's fill can be changed Scope `"stroke/change"` Explanation Whether the block's stroke can be changed Scope `"shape/change"` Explanation Whether the block's shape can be changed Scope `"text/edit"` Explanation Whether the block's text can be changed Scope `"text/character"` Explanation Whether the block's text properties can be changed ## Managing Scopes First, we globally defer the `"layer/move"` scope to the block-level using `engine.editor.setGlobalScope(key = "layer/move", globalScope = GlobalScope.DEFER)`. Since all blocks default to having their scopes set to `false` initially, modifying the layout properties of any block will fail at this point. Value Explanation Value `.allow` Explanation Manipulation of properties covered by the scope is always allowed Value `.deny` Explanation Manipulation of properties covered by the scope is always denied Value `.defer` Explanation Permission is deferred to the scope of the individual blocks ``` // Let the global scope defer to the block-level. engine.editor.setGlobalScope(key = "layer/move", globalScope = GlobalScope.DEFER) // Manipulation of layout properties of any block will fail at this point. try { engine.block.setPositionX(block, value = 100F) // Not allowed } catch (exception: Exception) { exception.printStackTrace() } ``` We can verify the current state of the global `"layer/move"` scope using `engine.editor.getGlobalScope(key = "layer/move")`. ``` // This will return `GlobalScope.DEFER`. engine.editor.getGlobalScope(key = "layer/move") ``` Now we can allow the `"layer/move"` scope for a single block by setting it to `true` using `fun setScopeEnabled(block: DesignBlock, key: String, enabled: Boolean)`. ``` // Allow the user to control the layout properties of the image block. engine.block.setScopeEnabled(block, key = "layer/move", enabled = true) // Manipulation of layout properties of any block is now allowed. try { engine.block.setPositionX(block, value = 100F) // Allowed } catch (exception: Exception) { exception.printStackTrace() } ``` Again we can verify this change by calling `fun isScopeEnabled(block: DesignBlock, key: String): Boolean`. ``` // Verify that the "layer/move" scope is now enabled for the image block. engine.block.isScopeEnabled(block, key = "layer/move") ``` Finally, `fun isAllowedByScope(block: DesignBlock, key: String): Boolean` will allow us to verify a block's final scope state by taking both the global state as well as block-level state into account. ``` // This will return true as well since the global scope is set to `GlobalScope.DEFER`. engine.block.isAllowedByScope(block, key = "layer/move") ``` How to Store Custom Metadata - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/store-metadata?language=kotlin&platform=android#setup # How to Store Custom Metadata CE.SDK allows you to store custom metadata in your scenes. You can attach metadata to your scene or directly to your individual design blocks within the scene. This metadata is persistent across saving and loading of scenes. It simply consists of key value pairs of strings. Using any string-based serialization format such as JSON will allow you to store even complex objects. Please note that when duplicating blocks their metadata will also be duplicated. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-store-metadata/StoreMetadata.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-store-metadata/StoreMetadata.kt) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` var scene = engine.scene.createFromImage( Uri.parse("https://img.ly/static/ubq_samples/imgly_logo.jpg"), ) val block = engine.block.findByType(DesignBlockType.Graphic).first() ``` ## Working with Metadata We can add metadata to any design block using `fun setMetadata(block: DesignBlock, key: String, value: String)`. This also includes the scene block. ``` engine.block.setMetadata(scene, key = "author", value = "img.ly") engine.block.setMetadata(block, key = "customer_id", value = "1234567890") engine.block.setMetadata(block, key = "customer_name", value = "Name") ``` We can retrieve metadata from any design block or scene using `fun getMetadata(block: DesignBlock, key: String): String`. Before accessing the metadata you check for its existence using `fun hasMetadata(block: DesignBlock, key: String): Boolean`. ``` // This will return "img.ly" engine.block.getMetadata(scene, key = "author") // This will return "1000000" engine.block.getMetadata(block, key = "customer_id") ``` We can query all metadata keys from any design block or scene using `fun findAllMetadata(block: DesignBlock): List`. For blocks without any metadata, this will return an empty list. ``` // This will return ["customer_id", "customer_name"] engine.block.findAllMetadata(block) ``` If you want to get rid of any metadata, you can use `fun removeMetadata(block: DesignBlock, key: String)`. ``` engine.block.removeMetadata(block, key = "customer_id") // This will return false engine.block.hasMetadata(block, key = "customer_id") ``` Metadata will automatically be saved and loaded as part the scene. So you don't have to worry about it getting lost or having to save it separately. ``` // We save our scene and reload it from scratch val sceneString = engine.scene.saveToString(scene) scene = engine.scene.load(scene = sceneString) // This still returns "img.ly" engine.block.getMetadata(scene, key = "author") // And this still returns "Name" engine.block.getMetadata(block, key = "customer_name") ``` Exploration - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-exploration?language=kotlin&platform=android#setup # Exploration Learn how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to explore scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun findAll(): List ``` Return all blocks currently known to the engine. * Returns a list of block ids. ``` val allIds = engine.block.findAll() ``` ``` fun findAllPlaceholders(): List ``` Return all placeholder blocks in the current scene. * Returns a list of block ids. ``` val allPlaceholderIds = engine.block.findAllPlaceholders() ``` ``` fun findByType(type: DesignBlockType): List ``` Finds all design blocks with the given type. * `type`: the type to search for. * Returns a list of block ids. ``` fun findByType(type: ShapeType): List ``` Finds all shape blocks with the given type. * `type`: the type to search for. * Returns a list of block ids. ``` fun findByType(type: EffectType): List ``` Finds all effect blocks with the given type. * `type`: the type to search for. * Returns a list of block ids. ``` fun findByType(type: BlurType): List ``` Finds all blur blocks with the given type. * `type`: the type to search for. * Returns a list of block ids. ``` val allPages = engine.block.findByType(DesignBlockType.Page) val allImageFills = engine.block.findByType(FillType.Image) val allStarShapes = engine.block.findByType(ShapeType.Star) val allHalfToneEffects = engine.block.findByType(EffectType.HalfTone) val allUniformBlurs = engine.block.findByType(BlurType.Uniform) ``` ``` fun findByKind(blockKind: String): List ``` Finds all blocks with the given kind. * `blockKind`: the kind to search for. * Returns a list of block ids. ``` val allStickers = engine.block.findByKind("sticker") ``` ``` fun findByName(name: String): List ``` Finds all blocks with the given name. * `name`: the name to search for. * Returns a list of block ids. ``` val ids = engine.block.findByName("someName") ``` Strokes - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-strokes?language=kotlin&platform=android#setup # Strokes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify strokes through the `block` API. Strokes can be added to any shape or text and stroke styles are varying from plain solid lines to dashes and gaps of varying lengths and can have different end caps. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Strokes ``` fun supportsStroke(block: DesignBlock): Boolean ``` Query if the given block has a stroke property. * `block`: the block to query. * Returns true if the block has a stroke property, false otherwise. ``` if (engine.block.supportsStroke(block)) { ``` ``` fun setStrokeEnabled( block: DesignBlock, enabled: Boolean, ) ``` Enable or disable the stroke of the given design block. Required scope: "stroke/change" * `block`: the block whose stroke should be enabled or disabled. * `enabled`: if true, the stroke will be enabled. ``` engine.block.setStrokeEnabled(block, enabled = true) ``` ``` fun isStrokeEnabled(block: DesignBlock): Boolean ``` Query if the stroke of the given design block is enabled. * `block`: the block whose stroke state should be queried. * Returns true if the block's stroke is enabled, false otherwise. ``` val strokeIsEnabled = engine.block.isStrokeEnabled(block) ``` ``` fun setStrokeColor( block: DesignBlock, color: Color, ) ``` Set the stroke color of the given design block. Required scope: "stroke/change" * `block`: the block whose stroke color should be set. * `color`: the color to set. ``` engine.block.setStrokeColor(block, color = Color.fromRGBA(r = 1F, g = 0.75F, b = 0.8F, a = 1F)) ``` ``` fun getStrokeColor(block: DesignBlock): Color ``` Get the stroke color of the given design block. * `block`: he block whose stroke color should be queried. * Returns the stroke color. ``` val strokeColor = engine.block.getStrokeColor(block) ``` ``` fun setStrokeWidth( block: DesignBlock, width: Float, ) ``` Set the stroke width of the given design block. Required scope: "stroke/change" * `block`: the block whose stroke width should be set. * `width`: the stroke width to be set. ``` engine.block.setStrokeWidth(block, width = 5F) ``` ``` fun getStrokeWidth(block: DesignBlock): Float ``` Get the stroke width of the given design block. * `block`: the block whose stroke width should be queried. * Returns the stroke's width. ``` val strokeWidth = engine.block.getStrokeWidth(block) ``` ``` fun setStrokeStyle( block: DesignBlock, style: StrokeStyle, ) ``` Set the stroke style of the given design block. Required scope: "stroke/change" * `block`: the block whose stroke style should be set. * `style`: the stroke style to be set. ``` engine.block.setStrokeStyle(block, style = StrokeStyle.DASHED) ``` ``` fun getStrokeStyle(block: DesignBlock): StrokeStyle ``` Get the stroke style of the given design block. * `block`: the block whose stroke style should be queried. * Returns the stroke's style. ``` val strokeStyle = engine.block.getStrokeStyle(block) ``` ``` fun setStrokePosition( block: DesignBlock, position: StrokePosition, ) ``` Set the stroke position of the given design block. Required scope: "stroke/change" * `block`: the block whose stroke position should be set. * `position`: the stroke position to be set. ``` engine.block.setStrokePosition(block, position = StrokePosition.OUTER) ``` ``` fun getStrokePosition(block: DesignBlock): StrokePosition ``` Get the stroke position of the given design block. * `block`: the block whose stroke position should be queried. * Returns the stroke position. ``` val strokePosition = engine.block.getStrokePosition(block) ``` ``` fun setStrokeCornerGeometry( block: DesignBlock, geometry: StrokeCornerGeometry, ) ``` Set the stroke corner geometry of the given design block. Required scope: "stroke/change" * `block`: the block whose stroke join geometry should be set. * `geometry`: the stroke join geometry to be set. ``` engine.block.setStrokeCornerGeometry(block, geometry = StrokeCornerGeometry.ROUND) ``` ``` fun getStrokeCornerGeometry(block: DesignBlock): StrokeCornerGeometry ``` Get the stroke corner geometry of the given design block. * `block`: the block whose stroke join geometry should be queried. * Returns the stroke join geometry. ``` val strokeCornerGeometry = engine.block.getStrokeCornerGeometry(block) ``` Using the Video Editor - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/solutions/video-editor?language=kotlin&platform=android # Using the Video Editor `Video Editor` is built to support versatile video editing capabilities for a broad range of video applications. In this example, we will show you how to initialize the `Video Editor` solution for the mobile editor on Android. The mobile editor is implemented entirely with Jetpack Compose and this example assumes that you use compose navigation, however, you can check the `Activity` and `Fragment` implementation samples on the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page. They can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-video-editor/VideoEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-video-editor/VideoEditorSolution.kt) ## Configuration In order to launch the `Video Editor`, you should initialize `EngineConfiguration` object. It is responsible for the configuration of the [Engine](/docs/cesdk/engine/quickstart/). All the properties other than the `onCreate` property have default values, so you do not have to worry about initializing them unless you need. Moreover, there is an `EngineConfiguration.rememberForVideo` helper function that only requires the `license` property. Note that you can override the `scene` that should be loaded in the `Video Editor` if you use the helper function. ``` val engineConfiguration = EngineConfiguration.rememberForVideo( license = "", userId = "", ) ``` Optionally, you can also provide an `EditorConfiguration` object if you want to configure the UI behavior of the `Video Editor`. If you do not provide a custom implementation, the editor will be launched with `EditorConfiguration.getDefault()` configuration object. ``` val editorConfiguration = EditorConfiguration.rememberForVideo() ``` For more details on how to use the configuration objects, visit [configuration](/docs/cesdk/mobile-editor/configuration/) page. ## Initialization After creating the configuration objects, launching the editor is straightforward. Just invoke the `VideoEditor` composable function from your composable context and pass the configuration objects that you have created. In addition, you also need to provide an implementation for the `onClose` callback. That defines what should happen when the editor close event is triggered. If you are not using compose navigation, you can check `Activity` and `Fragment` implementation samples in the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page for `onClose` implementations. ``` VideoEditor( engineConfiguration = engineConfiguration, editorConfiguration = editorConfiguration, ) { // You can set result here navController.popBackStack() } ``` Boolean Operations - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-bool-ops?language=kotlin&platform=android#setup # Boolean Operations In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to combine blocks through the `block` API with boolean operations. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Combining Combining blocks allows you to create a new block with a customized shape. Combining blocks with the `union`, `intersection` or `XOR` operation will result in the new block whose fill is that of the top-most block and whose shape is the result of applying the operation pair-wise on blocks from the top-most block to the bottom-most block. Combining blocks with the `difference` operation will result in the new block whose fill is that of the bottom-most block and whose shape is the result of applying the operation pair-wise on blocks from the bottom-most block to the top-most block. The combined blocks will be destroyed. #### Only the following blocks can be combined * A graphics block * A text block ``` fun isCombinable(blocks: List): Boolean ``` Checks whether blocks could be combined. Only graphics blocks and text blocks can be combined. All blocks must have the "lifecycle/duplicate" scope enabled. * `blocks`: blocks for which the confirm combinability. * Returns whether the blocks can be combined or an error. ``` if (engine.block.isCombinable(listOf(star, rect))) { ``` ``` fun combine( blocks: List, op: BooleanOperation, ): DesignBlock ``` Perform a boolean operation on the given blocks. All blocks must be combinable. See `isCombinable`. The parent, fill and sort order of the new block is that of the prioritized block. When performing a `Union`, `Intersection` or `XOR`, the operation is performed pair-wise starting with the element with the highest sort order. When performing a `Difference`, the operation is performed pair-wise starting with the element with the lowest sort order. Required scopes: "lifecycle/duplicate", "lifecycle/destroy" * `blocks`: blocks to combine. They will be destroyed if "lifecycle/destroy" scope is enabled. * `op`: boolean operation to perform. * Returns the newly created block or an error. ``` val combined = engine.block.combine(listOf(star, rect), op = BooleanOperation.UNION) ``` Using the Design Editor - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/solutions/design-editor?language=kotlin&platform=android#configuration # Using the Design Editor `Design Editor` is built to support versatile editing capabilities for a broad range of design applications. Toggling from edit and page overview modes enables users to quickly evaluate and change multi-page designs. A dock at the bottom of the editor provides quick access to most essential editing options in order of relevance allowing users to overlay text, add images, shapes, stickers and upload new image assets. In this example, we will show you how to initialize the `Design Editor` solution for the mobile editor on Android. The mobile editor is implemented entirely with Jetpack Compose and this example assumes that you use compose navigation, however, you can check the `Activity` and `Fragment` implementation samples on the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page. They can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-design-editor/DesignEditorSolution.kt). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-solutions-design-editor/DesignEditorSolution.kt) ## Configuration In order to launch the `Design Editor`, you should initialize `EngineConfiguration` object. It is responsible for the configuration of the [Engine](/docs/cesdk/engine/quickstart/). All the properties other than the `onCreate` property have default values, so you do not have to worry about initializing them unless you need. Moreover, there is an `EngineConfiguration.rememberForDesign` helper function that only requires the `license` property. Note that you can override the `scene` that should be loaded in the `Design Editor` if you use the helper function. ``` val engineConfiguration = EngineConfiguration.rememberForDesign( license = "", userId = "", ) ``` Optionally, you can also provide an `EditorConfiguration` object if you want to configure the UI behavior of the `Design Editor`. If you do not provide a custom implementation, the editor will be launched with `EditorConfiguration.getDefault()` configuration object. ``` val editorConfiguration = EditorConfiguration.rememberForDesign() ``` For more details on how to use the configuration objects, visit [configuration](/docs/cesdk/mobile-editor/configuration/) page. ## Initialization After creating the configuration objects, launching the editor is straightforward. Just invoke the `DesignEditor` composable function from your composable context and pass the configuration objects that you have created. In addition, you also need to provide an implementation for the `onClose` callback. That defines what should happen when the editor close event is triggered. If you are not using compose navigation, you can check `Activity` and `Fragment` implementation samples in the [quickstart](/docs/cesdk/mobile-editor/quickstart/) page for `onClose` implementations. ``` DesignEditor( engineConfiguration = engineConfiguration, editorConfiguration = editorConfiguration, ) { // You can set result here navController.popBackStack() } ``` Reading and modifying text properties in the Headless CreativeEngine - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/guides/text-properties?language=kotlin&platform=android#editing-the-text-string # Reading and modifying text properties in the Headless CreativeEngine In this example, we want to show how to read and modify the text block's contents via the API in the offscreen Engine. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-text-with-emojis/UsingFills.kt) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/engine-guides-text-with-emojis/UsingFills.kt) ## Editing the Text String You can edit the text string contents of a text block using the `fun replaceText(block: DesignBlock, text: String, from: Int = -1, to: Int = -1)` and `fun removeText(block: DesignBlock, from: Int = -1, to: Int = -1)` APIs. The range of text that should be edited is defined using the UTF-16 indices \[from, to). When omitting both the `from` and `to` arguments, the entire existing string is replaced. ``` engine.block.replaceText(text, text = "Hello World") ``` When only specifying the `from` index, the new text is inserted at this index. ``` // Add a "!" at the end of the text engine.block.replaceText(text, text = "!", from = 11) ``` When both `from` and `to` indices are specified, then that range of text is replaced with the new text. ``` // Replace "World" with "Alex" engine.block.replaceText(text, text = "Alex", from = 6, to = 11) ``` Similarly, the `removeText` API can be called to remove either a specific range or the entire text. ``` // Remove the "Hello " engine.block.removeText(text, from = 0, to = 6) ``` ## Text Colors Text blocks in the Engine allow different ranges to have multiple colors. Use the `fun setTextColor(block: DesignBlock, color: Color, from: Int = -1, to: Int = -1)` API to change either the color of the entire text ``` engine.block.setTextColor(text, color = Color.fromHex("#FFFF0000")) ``` or only that of a range. After these two calls, the text "Alex!" now starts with one yellow character, followed by three black characters and two more yellow ones. ``` engine.block.setTextColor(text, color = Color.fromHex("#FFFF0000"), from = 1, to = 4) ``` The `fun getTextColors(block: DesignBlock, from: Int = -1, to: Int = -1): List` API returns an ordered list of unique colors in the requested range. Here, `allColors` will be a list containing the colors yellow and black (in this order). ``` val allColors = engine.block.getTextColors(text) ``` When only the colors in the UTF-16 range from 2 to 5 are requested, the result will be an array containing black and then yellow, since black appears first in the requested range. ``` val colorsInRange = engine.block.getTextColors(text, from = 2, to = 5) ``` ## Text Background You can create and edit the background of a text block by setting specific block properties. To add a colored background to a text block use the `fun setBoolean(block: DesignBlock, property: String, value: Boolean))` API and enable the `backgroundColor/enabled` property. ``` engine.block.setBoolean(text, property = "backgroundColor/enabled", value = true) ``` The color of the text background can be queried (by making use of the `fun setColor(block: DesignBlock, property: String, value: Color)` API ) and also changed (with the `fun setColor(block: DesignBlock, property: String, value: Color)` API). ``` val color = engine.block.getColor(text, property = "backgroundColor/color") ``` The padding of the rectangular background shape can be edited by using the `fun setFloat(block: DesignBlock, property: String, value: Float)` API and setting the target value for the desired padding property like: * `backgroundColor/paddingLeft`: * `backgroundColor/paddingRight`: * `backgroundColor/paddingTop`: * `backgroundColor/paddingBottom`: ``` engine.block.setFloat(text, property = "backgroundColor/paddingLeft", value = 1.0F) ``` Additionally, the rectangular shape of the background can be rounded by setting a corner radius with the `fun setFloat(block: DesignBlock, property: String, value: Float)` API to adjust the value of the `backgroundColor/cornerRadius` property. ``` engine.block.setFloat(text, property = "backgroundColor/cornerRadius", value = 4.0F) ``` Text backgrounds inherit the animations assigned to their respective text block when the animation text writing style is set to `Block`. ``` val animation = engine.block.createAnimation(AnimationType.Slide) ``` ## Text Case You can apply text case modifications to ranges of text in order to display them in upper case, lower case or title case. It is important to note that these modifiers do not change the `text` string value of the text block but are only applied when the block is rendered. By default, the text case of all text within a text block is set to `TextCase.NORMAL`, which does not modify the appearance of the text at all. The `fun setTextCase(block: DesignBlock, textCase: TextCase, from: Int = -1, to: Int = -1)` API sets the given text case for the selected range of text. Possible values for `TextCase` are: * `NORMAL`: The text string is rendered without modifications. * `UPPER_CASE`: All characters are rendered in upper case. * `LOWER_CASE`: All characters are rendered in lower case. * `TITLE_CASE`: The first character of each word is rendered in upper case. ``` engine.block.setTextCase(text, textCase = TextCase.TITLE_CASE) ``` The `fun getTextCases(block: DesignBlock, from: Int = -1, to: Int = -1): List` API returns the ordered list of text cases of the text in the selected range. ``` val textCases = engine.block.getTextCases(text) ``` ## Typefaces In order to change the font of a text block, you have to call the `fun setFont(block: DesignBlock, fontFileUri: Uri, typeface: Typeface)` API and provide it with both the uri of the font file to be actively used and the complete typeface definition of the corresponding typeface. Existing formatting of the block is reset. A typeface definition consists of the unique typeface name (as it is defined within the font files), and a list of all font definitions that belong to this typeface. Each font definition must provide a `Uri` which points to the font file and a `subFamily` string which is this font's effective name within its typeface. The subfamily value is typically also defined within the font file. The `weight` and `style` properties default to `NORMAL`, but must be provided in other cases. For the sake of this example, we define a `Roboto` typeface with only four fonts: `Regular`, `Bold`, `Italic`, and `Bold Italic` and we change the font of the text block to the Roboto Regular font. ``` val typeface = Typeface( name = "Roboto", fonts = listOf( Font( uri = Uri.parse("https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf"), subFamily = "Bold", weight = FontWeight.BOLD, style = FontStyle.NORMAL, ), Font( uri = Uri.parse("https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf"), subFamily = "Bold Italic", weight = FontWeight.BOLD, style = FontStyle.ITALIC, ), Font( uri = Uri.parse("https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf"), subFamily = "Italic", weight = FontWeight.BOLD, style = FontStyle.NORMAL, ), Font( uri = Uri.parse("https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf"), subFamily = "Regular", weight = FontWeight.NORMAL, style = FontStyle.NORMAL, ), ), ) engine.block.setFont(text, typeface.fonts[3].uri, typeface) ``` If the formatting, e.g., bold or italic, of the text should be kept, you have to call the `fun setTypeface(block: DesignBlock, fontFileUri: Uri, typeface: Typeface)` API and provide it with both the uri of the font file to be used and the complete typeface definition of the corresponding typeface. The font file should be a fallback font, e.g., `Regular`, from the same typeface. The actual font that matches the formatting is chosen automatically with the current formatting retained as much as possible. If the new typeface does not support the current formatting, the formatting changes to a reasonable close one, e.g. thin might change to light, bold to normal, and/or italic to non-italic. If no reasonable font can be found, the fallback font is used. ``` engine.block.setTypeface(text, typeface, from = 1, to = 4) engine.block.setTypeface(text, typeface) ``` You can query the currently used typeface definition of a text block by calling the `fun getTypeface(block: DesignBlock): Typeface` API. It is important to note that new text blocks don't have any explicit typeface set until you call the `setFont` API. In this case, the `getTypeface` API will throw an error. ``` val currentDefaultTypeface = engine.block.getTypeface(text) ``` ## Font Weights and Styles Text blocks can also have multiple ranges with different weights and styles. In order to toggle the text of a text block between the normal and bold font weights, first call the `fun canToggleBoldFont(block: DesignBlock, from: Int = -1, to: Int = -1): Boolean` API to check whether such an edit is possible and if so, call the `fun toggleBoldFont(block: DesignBlock, from: Int = -1, to: Int = -1)` API to change the weight. ``` if (engine.block.canToggleBoldFont(text)) { engine.block.toggleBoldFont(text) } if (engine.block.canToggleBoldFont(text, from = 1, to = 4)) { engine.block.toggleBoldFont(text, from = 1, to = 4) } ``` In order to toggle the text of a text block between the normal and italic font styles, first call the `fun canToggleItalicFont(block: DesignBlock, from: Int = -1, to: Int = -1): Boolean` API to check whether such an edit is possible and if so, call the `fun toggleItalicFont(block: DesignBlock, from: Int = -1, to: Int = -1)` API to change the style. ``` if (engine.block.canToggleItalicFont(text)) { engine.block.toggleItalicFont(text) } if (engine.block.canToggleItalicFont(text, from = 1, to = 4)) { engine.block.toggleItalicFont(text, from = 1, to = 4) } ``` In order to change the font weight or style, the typeface definition of the text block must include a font definition that corresponds to the requested font weight and style combination. For example, if the text block currently uses a bold font and you want to toggle the font style to italic - such as in the example code - the typeface must contain a font that is both bold and italic. The `fun getTextFontWeights(block: DesignBlock, from: Int = -1, to: Int = -1): List` API returns an ordered list of unique font weights in the requested range, similar to the `getTextColors` API described above. For this example text, the result will be `listOf(FontWeight.BOLD)`. ``` val fontWeights = engine.block.getTextFontWeights(text) ``` The `fun getTextFontStyles(block: DesignBlock, from: Int = -1, to: Int = -1): List` API returns an ordered list of unique font styles in the requested range, similar to the `getTextColors` API described above. For this example text, the result will be `listOf(FontWeight.ITALIC)`. ``` val fontStyles = engine.block.getTextFontStyles(text) ``` History - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/editor-history?language=kotlin&platform=android#setup # History In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to undo and redo steps in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` fun createHistory(): History ``` * Brief: Create a history which consists of an undo/redo stack for editing operations. There can be multiple. But only one can be active at a time. * Returns the handle to the created history. ``` val newHistory = engine.editor.createHistory() ``` ``` fun destroyHistory(history: History) ``` Destroy the given history, returns an error if the handle doesn't refer to a history. * `history`: the history to be destroyed. ``` engine.editor.destroyHistory(oldHistory) ``` ``` fun setActiveHistory(history: History) ``` Mark the given history as active, returns an error if the handle doesn't refer to a history. All other histories get cleared from the active state. Undo/redo operations only apply to the active history. * `history`: the history to be marked as active. ``` engine.editor.setActiveHistory(newHistory) ``` ``` fun getActiveHistory(): History ``` Get the handle to the currently active history. If there's none it will be created. * Returns the handle to the active history. ``` val oldHistory = engine.editor.getActiveHistory() ``` ``` fun addUndoStep() ``` Adds a new history state to the stack, if undoable changes were made. ``` engine.editor.addUndoStep() ``` ``` fun undo() ``` Undo one step in the history if an undo step is available. ``` engine.editor.undo() ``` ``` fun canUndo(): Boolean ``` If an undo step is available. * Returns true if an undo step is available. ``` if (engine.editor.canUndo()) { ``` ``` fun redo() ``` Redo one step in the history if a redo step is available. ``` engine.editor.redo() ``` ``` fun canRedo(): Boolean ``` If a redo step is available. * Returns true if a redo step is available. ``` if (engine.editor.canRedo()) { ``` ``` fun onHistoryUpdated(): Flow ``` Subscribe to changes to the undo/redo history. * Returns flow of history updates. ``` engine.editor.onHistoryUpdated() .onEach { println("Editor history updated") } .launchIn(CoroutineScope(Dispatchers.Main)) ``` Hierarchies - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-hierarchies?language=kotlin&platform=android#setup # Hierarchies In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify the hierarchy of blocks through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Manipulate the hierarchy of blocks Only blocks that are direct or indirect children of a `page` block are rendered. Scenes without any `page` child may not be properly displayed by the CE.SDK editor. ``` fun getParent(block: DesignBlock): DesignBlock? ``` Query a block's parent. * `block`: the block to query. * Returns the parent's handle or null if the block has no parent. ``` val parent = engine.block.getParent(block) ``` ``` fun getChildren(block: DesignBlock): List ``` Get all children of the given block. Children are sorted in their rendering order: Last child is rendered in front of other children. * `block`: the block to query. * Returns a list of block ids. ``` val childIds = engine.block.getChildren(block) ``` ``` fun insertChild( parent: DesignBlock, child: DesignBlock, index: Int, ) ``` Insert a new or existing child at a certain position in the parent's children. Required scope: "editor/add" * `parent`: the block to update. * `child`: the child to insert. Can be an existing child of `parent`. * `index`: the index to insert or move to. ``` engine.block.insertChild(parent = page, child = block, index = 0) ``` ``` fun appendChild( parent: DesignBlock, child: DesignBlock, ) ``` Appends a new or existing child to a block's children. Required scope: "editor/add" * `parent`: the block to update. * `child`: the child to insert. Can be an existing child of `parent`. ``` engine.block.appendChild(parent = parent, child = block) ``` When adding a block to a new parent, it is automatically removed from its previous parent. Animations - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-animations/?platform=android&language=kotlin Platform Web Node.JS iOS Catalyst macOS Android Language Kotlin Platform: Android Language: Kotlin [ Previous Types ](/docs/cesdk/engine/api/block-types/)[ Next Animation Types ](/docs/cesdk/engine/api/block-animation-types/) Integrate the Mobile Editor - CE.SDK | IMG.LY Docs [android/fragment/kotlin] https://img.ly/docs/cesdk/mobile-editor/quickstart/?platform=android&language=kotlin&framework=fragment # Integrate the Mobile Editor In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile editor in your Android app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) ## Adding dependency Add IMG.LY maven repository to the list of maven urls in the `settings.gradle` file. ``` maven { name "IMG.LY Artifactory" url "https://artifactory.img.ly/artifactory/maven" mavenContent { includeGroup("ly.img") } } ``` Add editor dependency in the `build.gradle` file of your application module. ``` implementation "ly.img:editor:1.43.0" // Add this as well if you want to use img.ly camera's powerful features. // If not, then system camera will be used everywhere. implementation "ly.img:camera:1.43.0" ``` ## Requirements In order to use the mobile editor, your application should meet the following requirements: * `buildFeatures.compose` should be `true`, as the editor is written in Jetpack Compose. ``` buildFeatures { compose true } ``` * `composeOptions.kotlinCompilerExtensionVersion` should match the kotlin version. Use the official compatibility map in [here](https://developer.android.com/jetpack/androidx/releases/compose-kotlin). ``` composeOptions { kotlinCompilerExtensionVersion = "1.5.3" } ``` * `compose-bom` version is `2023.05.01` or higher if your project uses Jetpack Compose dependencies. Note that using lower versions may cause crashes and issues in your own compose code, as our version will override yours. In case you are not using BOM, you can find the BOM to compose library version mapping in [here](https://developer.android.com/jetpack/compose/bom/bom-mapping). ``` implementation(platform("androidx.compose:compose-bom:2023.05.01")) ``` * Kotlin version is 1.9.10 or higher. * `minSdk` is 24 (Android 7) or higher. ``` minSdk 24 ``` By default, the mobile editor supports following ABIs: `arm64-v8a`, `armeabi-v7a`, `x86_64` and `x86`. If you want to filter out some of the ABIs, use `abiFilters`. ``` ndk { abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" } ``` ## Usage In this example, the basic usage of the mobile editor is going to be demonstrated using the [Design Editor](/docs/cesdk/mobile-editor/solutions/design-editor/) solution, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). In order to launch the editor, an `EngineConfiguration` object should be provided. Use the `EngineConfiguration.rememberForDesign()` for the simplest implementation. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. #### Warning Other than the _userId_ we also use the [Settings.Secure.ANDROID\_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) for better data accuracy. You should include it in the _Data safety form_ of your application when uploading it to the Play Store. ``` val engineConfiguration = EngineConfiguration.rememberForDesign( license = "", userId = "", ) ``` Afterwards, invoke `DesignEditor` composable function from your composable context. Other than the `EngineConfiguration`, you should also provide an `onClose` callback as the last parameter. That defines what should happen when the editor close event is triggered. ``` DesignEditor(engineConfiguration = engineConfiguration) { // You can set result here parentFragmentManager.popBackStack() } ``` That is all. Check all the available [solutions](/docs/cesdk/mobile-editor/solutions/) in order to decide which solution fits you best. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-editor/configuration/). State - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/block-state?language=kotlin&platform=android#setup # State In this example, we will show you how to use [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to retrieve's a block's state and to manually set a block in a pending state, an error state or back to a ready state. Blocks can perform operations that take some time or that can end in bad results. When that happens, blocks put themselves in a pending state or an error state and visual feedback is shown pertaining to the state. When an external operation is done to blocks, for example with a plugin, you may want to manually set the block's state to pending (if that external operation takes time) or to error (if that operation resulted in an error). The possible states of a block are: When calling `getState`, the returned state reflects the combined state of a block, the block's fill, the block's shape and the block's effects. If any of these blocks is in an `Error` state, the returned state will reflect that error. If none of these blocks is in error state but any is in `Pending` state, the returned state will reflect the aggregate progress of the block's progress. If none of the blocks are in error state or pending state, the returned state is `Ready`. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` fun getState(block: DesignBlock): BlockState ``` Get the current state of a block. Note If this block is in error state or this block has a `Shape` block, `Fill` block or `Effect` block(s), that is in error state, the returned state will be `BlockState.Error`. Else, if this block is in pending state or this block has a `Shape` block, `Fill` block or `Effect` block(s), that is in pending state, the returned state will be `BlockState.Pending`. Else, the returned state will be `BlockState.Ready`. * `block`: the block whose state should be queried. * Returns the state of the block. ``` val state = engine.block.getState(block) ``` ``` fun setState( block: DesignBlock, state: BlockState, ) ``` Set the state of a block. * `block`: the block whose state should be set. * `state`: the new state to set. ``` engine.block.setState(block, state = BlockState.Pending(progress = 0.5F)) engine.block.setState(block, state = BlockState.Ready) engine.block.setState(block, state = BlockState.Error(BlockState.Error.Type.IMAGE_DECODING)) ``` ``` fun onStateChanged(blocks: List): Flow> ``` Subscribe to changes to the state of a block. Like `getState`, the state of a block is determined by the state of itself and its `Shape`, `Fill` and `Effect` block(s). * `blocks`: a list of blocks to filter events by. If the list is empty, events for every block are sent. * Returns flow of block state change events. ``` engine.block.onStateChanged(listOf(block)) .onEach { println("State of blocks $it is updated.") } .launchIn(CoroutineScope(Dispatchers.Main)) ``` Get started with CE.SDK Engine - CE.SDK | IMG.LY Docs [android/composable/kotlin] https://img.ly/docs/cesdk/engine/quickstart/?platform=android&language=kotlin&framework=composable # Get started with CE.SDK Engine In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s engine in your Android app. The engine enables you to power your own UI and creative workflows. Whether you want to keep your current UI or design a new one from scratch, our API can do the heavy lifting for you no matter what your starting point is. Explore a full code sample on [Github](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) . * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0) ### Requirements Creative Engine requires Android 7 or higher (minSdk 24) and Kotlin version 1.6.0 or higher. ## Adding dependency Add img.ly maven repository to the list of maven urls in the `settings.gradle` file. ``` maven { name "IMG.LY Artifactory" url "https://artifactory.img.ly/artifactory/maven" mavenContent { includeGroup("ly.img") } } ``` Add engine dependency in the `build.gradle` file of your application module. ``` implementation "ly.img:engine:1.43.0" ``` By default, the engine supports following ABIs: `arm64-v8a`, `armeabi-v7a`, `x86_64` and `x86`. If you want to filter out some of the ABIs, use `abiFilters`. ``` ndk { abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" } ``` ## Initialization Before any usage, `application` instance of your app needs to be passed to the `Engine`. The best place to make this initialization is the `onCreate` method of your custom `Application` class. ``` Engine.init(this) ``` ## Usage The `Engine` is a class that accepts id in the constructor. Note that if an `Engine` instance already exists with such id, then the newly created instance will internally be the same, i.e. scene will be the same. Also note that the id should not contain any forward slashes. Engine's lifecycle is not tied to the lifecycle of android components such as `Activity` or `View` and therefore, must be handled manually. ``` val engine = remember { Engine.getInstance(id = "ly.img.engine.example") } ``` In order to use the instance, call `start()`. You should provide the license key that you received from IMG.LY. Optionally, you can provide unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Optionally, you can also pass `SavedStateRegistryOwner` object (i.e. `AppCompatActivity`, `androidx.Fragment`, `LocalSavedStateRegistryOwner` in Jetpack Compose etc.) It is highly recommended that you pass this object as it can save/restore current scene of the Engine when application is terminated/recreated due to low memory (or when Activity Kill Mode is enabled). Note that it is safe to call `start()` multiple times, however, it will return `true` only the first time. Also note that any interaction with the engine has to be on the main thread. Any interaction on any other thread will cause thrown exceptions. #### Warning Other than the _userId_ we also use the [Settings.Secure.ANDROID\_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) for better data accuracy. You should include it in the _Data safety form_ of your application when uploading it to the Play Store. ``` engine.start( license = "", userId = "", savedStateRegistryOwner = savedStateRegistryOwner, ) ``` Call one of the `bind` methods to add a target onto which engine should draw. Currently, it is possible to pass `SurfaceView`, `TextureView` and `SurfaceHolder` as target canvas. Also, it is possible to draw offscreen (without any UI) by only providing size of the canvas. ``` engine.bindSurfaceView(surfaceView) ``` Whenever you want the engine to stop drawing onto the target, call `unbind()`. Note that the engine holds hard reference to the target UI component and therefore, not calling `unbind` can cause memory leaks. Also note that you cannot have multiple bound targets: every `bind()` call internally calls `unbind()` on previous target. ``` event == Lifecycle.Event.ON_DESTROY -> engine.unbind() ``` After these setup steps, you can use our APIs to interact with the Engine. [The next couple of pages](/docs/cesdk/engine/guides/) will document the methods available. ``` engine.scene.get() ?: run { val sceneUri = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene") engine.scene.load(sceneUri) } ``` Finally, when you do not need to use the engine anymore, call `stop()`. This resets the engine and clears all the JVM and native allocated memory since the previous `start()` call. In order to use the engine again after stop, call `start()`. ``` event == Lifecycle.Event.ON_DESTROY && !activity.isChangingConfigurations -> engine.stop() ``` Configure Asset Library - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/asset-library/?language=kotlin&platform=android # Configure Asset Library In this example, we will show you how to customize the asset library for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-asset-library/). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-asset-library/) ## Configuration Asset library configuration is part of the `EditorConfiguration` class. Use the `EditorConfiguration.getDefault` helper function to make asset library configurations. * `assetLibrary` - the asset library UI definition used by the editor. It defines the content of the tabs when floating action button is clicked as well as the content of sheets when images and stickers are replaced. By default, `AssetLibrary.getDefault()` is used. ``` val editorConfiguration = EditorConfiguration.rememberForDesign( assetLibrary = AssetLibrary.getDefault(), ) ``` ### Custom Asset Source To use custom asset sources in the asset library UI, the custom asset source must be first added to the engine. In addition to creating or loading a scene, registering the asset sources should be done in the [`onCreate` callback](/docs/cesdk/mobile-editor/configuration/callbacks/). In this example, the `EditorDefaults.onCreate` default implementation is used and afterwards, the [custom `UnsplashAssetSource`](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) is added. ``` val unsplashAssetSource = remember { UnsplashAssetSource(Secrets.unsplashHost) } val engineConfiguration = EngineConfiguration.remember( license = "", onCreate = { EditorDefaults.onCreate( engine = editorContext.engine, sceneUri = EngineConfiguration.defaultDesignSceneUri, eventHandler = editorContext.eventHandler, ) { _, _ -> editorContext.engine.asset.addSource(unsplashAssetSource) } }, ) ``` ### Default Asset Library `AssetLibrary.getDefault()` provides a simple API in case you want to reorder/drop predefined tabs, as well as adjust the content of the tabs. In this example, firstly, we drop the `Elements` tab and reorder the remaining ones and secondly, we manipulate the sections of the `images` tab by dropping, swapping and appending sections. Note that we append the section based on the `UnsplashAssetSource` shown above. ``` val assetLibrary = remember { val unsplashSection = LibraryContent.Section( titleRes = R.string.unsplash, sourceTypes = listOf(AssetSourceType(sourceId = unsplashAssetSource.sourceId)), assetType = AssetType.Image, ) AssetLibrary.getDefault( tabs = listOf( AssetLibrary.Tab.IMAGES, AssetLibrary.Tab.SHAPES, AssetLibrary.Tab.STICKERS, AssetLibrary.Tab.TEXT, ), images = LibraryCategory.Images .replaceSection(index = 0) { // We replace the title: "Image Uploads" -> "Uploads" copy(titleRes = R.string.uploads) } .dropSection(index = 1) .addSection(unsplashSection), ) } val editorConfiguration = EditorConfiguration.rememberForDesign( assetLibrary = assetLibrary, ) ``` ### Custom Asset Library In case you want to adjust the asset library even further (i.e. create your own completely custom tabs, use different `images` category in FAB and replace sheets etc), you should use the constructor of the `AssetLibrary` instead of the helper `AssetLibrary.getDefault()` function. In this example, firstly, we fill the tabs with a custom `My Assets` tab as well as with the default `images` tab and secondly, we use a different `images` category for the replace sheet, which contains the default `images` + `UnsplashAssetSource` section. ``` val assetLibrary = remember { // We create a custom tab with title "My Assets" that contains 2 sections: // 1. Stickers - expanding it opens the default stickers content // 2. Text - expanding it opens the default text content. Note that the title is skipped. val myAssetsCategory = LibraryCategory( tabTitleRes = SmokeTestR.string.my_assets, tabSelectedIcon = IconPack.LibraryElements, tabUnselectedIcon = IconPack.LibraryElements, content = LibraryContent.Sections( titleRes = SmokeTestR.string.my_assets, sections = listOf( LibraryContent.Section( titleRes = R.string.ly_img_editor_stickers, sourceTypes = LibraryContent.Stickers.sourceTypes, assetType = AssetType.Sticker, expandContent = LibraryContent.Stickers, ), LibraryContent.Section( sourceTypes = LibraryContent.Text.sourceTypes, assetType = AssetType.Text, expandContent = LibraryContent.Text, ), ), ), ) AssetLibrary( tabs = { listOf( myAssetsCategory, LibraryCategory.Images, ) }, images = { val unsplashSection = LibraryContent.Section( titleRes = SmokeTestR.string.unsplash, sourceTypes = listOf(AssetSourceType(sourceId = unsplashAssetSource.sourceId)), assetType = AssetType.Image, ) // See how the images is different in tabs and here LibraryCategory.Images.addSection(unsplashSection) }, ) } val editorConfiguration = EditorConfiguration.rememberForDesign( assetLibrary = assetLibrary, ) ``` Configure Overlay - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/mobile-editor/configuration/overlay/?platform=android&language=kotlin # Configure Overlay In this example, we will show you how to make overlay configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-overlay). * [View on GitHub](https://github.com/imgly/cesdk-android-examples/tree/v1.43.0/editor-guides-configuration-overlay) ## Configuration When working with [UI events](/docs/cesdk/mobile-editor/configuration/ui-events/), you may want to update the UI that is drawn over the editor (a.k.a `Overlay`) upon receiving events. A great example would be showing a loading indicator when the editor is being loaded or showing a custom dialog asking for export configurations when your customer taps on the `Export` button. This is when overlay configuration comes to help. In this example, we are going to demonstrate how upon capturing show/hide loading events you can render your own loading dialog. The default `EditorUiState` already contains `showLoading` property that is responsible for drawing an overlaying loading. Although we can use the same property, let's create our own state class in order to demonstrate how the default state can be wrapped and extended. The only requirement of the state class is that it has to be `Parcelable`. ``` data class OverlayCustomState( // hide default loading so we can use custom loading val baseState: EditorUiState = EditorUiState(showLoading = false), val showCustomLoading: Boolean = true, ) : Parcelable { constructor(parcel: Parcel) : this( baseState = ParcelCompat.readParcelable(parcel, EditorUiState::class.java.classLoader, EditorUiState::class.java)!!, showCustomLoading = parcel.readByte() != 0.toByte(), ) override fun writeToParcel( parcel: Parcel, flags: Int, ) { parcel.writeParcelable(baseState, flags) parcel.writeByte(if (showCustomLoading) 1 else 0) } override fun describeContents() = 0 companion object CREATOR : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): OverlayCustomState { return OverlayCustomState(parcel) } override fun newArray(size: Int): Array { return arrayOfNulls(size) } } } ``` By default, both `onCreate` and `onExport` [callbacks](/docs/cesdk/mobile-editor/configuration/callbacks/) send `ShowLoading` and `HideLoading` UI events. All we have to do is capture these events and override the default behavior. Similar to the [UI events](/docs/cesdk/mobile-editor/configuration/ui-events/) guide, the events are captured and an updated state is returned. Note that instead of updating the `EditorUiState.showLoading` property we update the property of the brand new state: `OverlayCustomState.showCustomLoading`. ``` onEvent = { state, event -> when (event) { is ShowLoading -> { state.copy(showCustomLoading = true) } is HideLoading -> { state.copy(showCustomLoading = false) } else -> { // handle other default events state.copy(baseState = EditorDefaults.onEvent(editorContext.activity, state.baseState, event)) } } }, ``` As the state is updated, the updated state is received in the `overlay` composable callback to be rendered. Finally, we can render our custom loading dialog and render remaining default overlay components via `EditorDefaults.Overlay` composable function. Note that the callback receives `EditorEventHandler` object too in case your composable component requires UI interaction. In this example, tapping on the confirm button of the loading dialog closes the dialog and the editor. ``` overlay = { state -> if (state.showCustomLoading) { AlertDialog( onDismissRequest = { }, title = { Text(text = "Please wait. If you want to close the editor, click the button.") }, confirmButton = { TextButton( onClick = { editorContext.eventHandler.send(HideLoading) editorContext.eventHandler.send(EditorEvent.CloseEditor()) }, ) { Text(text = "Close") } }, properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false), ) } EditorDefaults.Overlay(state = state.baseState, eventHandler = editorContext.eventHandler) }, ``` Note that the overlay is edge-to-edge, therefore it is your responsibility to draw over system bars too. Manage Assets - CE.SDK | IMG.LY Docs [android/undefined/kotlin] https://img.ly/docs/cesdk/engine/api/assets/?language=kotlin&platform=android # Manage Assets In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage assets through the `asset` API. To begin working with assets first you need at least one asset source. As the name might imply asset sources provide the engine with assets. These assets then show up in the editor's asset library. But they can also be independently searched and used to create design blocks. Asset sources can be added dynamically using the `asset` API as we will show in this guide. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Defining a Custom Asset Source Asset sources need at least an `id` and a `findAssets` function. You may notice `findAssets` method is `suspend`. This way you can use web requests or other long-running operations inside them and return results asynchronously. Let's go over these member one by one: All functions of `AssetApi` refer to an asset source by its unique `id`. That's why it has to be mandatory. Trying to add an asset source with an already registered `id` will fail. ``` class UnsplashAssetSource : AssetSource(sourceId = "ly.img.asset.source.unsplash") { ``` ``` abstract suspend fun findAssets(query: FindAssetsQuery): FindAssetsResult ``` Searches assets based on the `query`. * `query`: the object that is used to filter results. * Returns asset search result. ``` override suspend fun findAssets(query: FindAssetsQuery): FindAssetsResult { return if (query.query.isNullOrEmpty()) query.getPopularList() else query.getSearchList() } private suspend fun FindAssetsQuery.getPopularList(): FindAssetsResult { val queryParams = listOf( "order_by" to "popular", "page" to page, "perPage" to perPage ).joinToString(separator = "&") { (key, value) -> "$key=$value" } val assetsArray = getResponseAsString("$BASE_URL/photos?$queryParams").let(::JSONArray) return FindAssetsResult( assets = (0 until assetsArray.length()).map { assetsArray.getJSONObject(it).toAsset() }, currentPage = page, nextPage = page + 1, total = 0 ) } private suspend fun FindAssetsQuery.getSearchList(): FindAssetsResult { val queryParams = listOf( "query" to query, "page" to page, "perPage" to perPage ).joinToString(separator = "&") { (key, value) -> "$key=$value" } val response = getResponseAsString("$BASE_URL/search/photos?$queryParams").let(::JSONObject) val assetsArray = response.getJSONArray("results") val total = response.getInt("total") val totalPages = response.getInt("total_pages") return FindAssetsResult( assets = (0 until assetsArray.length()).map { assetsArray.getJSONObject(it).toAsset() }, currentPage = page, nextPage = if (page == totalPages) -1 else page + 1, total = total ) } ``` ``` abstract suspend fun getGroups(): List? ``` Specifies all the available groups in this asset source. * Returns list of available groups. ``` override suspend fun getGroups(): List? = null ``` ``` open val credits: AssetCredits? = null ``` Returns the credits info of this asset source. By default it is null. * Returns the credits info. ``` override val credits = AssetCredits( name = "Unsplash", uri = Uri.parse("https://unsplash.com/") ) ``` ``` open val license: AssetLicense? = null ``` Returns the license info of this asset source. By default it is null. * Returns the license info. ``` override val license = AssetLicense( name = "Unsplash license (free)", uri = Uri.parse("https://unsplash.com/license") ) ``` ## Registering a New Asset Source ``` fun addSource(source: AssetSource) ``` Adds a custom asset source. Its ID has to be unique. * `source`: the asset source. ``` engine.asset.addSource(source) ``` ``` fun addLocalSource( sourceId: String, supportedMimeTypes: List, applyAsset: (suspend (Asset) -> DesignBlock?)? = null, applyAssetToBlock: (suspend (Asset, DesignBlock) -> Unit)? = null, ) ``` Adds a local asset source. Its ID has to be unique. * `sourceId`: the id of the new local asset source. * `supportedMimeTypes`: the mime types of assets that are allowed to be added to this local source. * `applyAsset`: an optional callback that can be used to override the default behavior of applying a given asset result to the active scene. * `applyAssetToBlock`: an optional callback that can be used to override the default behavior of applying an asset result to a given block. ``` val localSourceId = "LocalSourceId" engine.asset.addLocalSource(localSourceId, supportedMimeTypes = "image/jpeg") ``` ``` fun findAllSources(): List ``` Finds all registered asset sources. * Returns a list with the IDs of all registered asset sources. ``` val assetSourceIds = engine.asset.findAllSources() // List [ "ly.img.asset.source.unsplash", "LocalSourceId", ... ] ``` ``` fun removeSource(sourceId: String) ``` Removes an asset source with the given ID. * `sourceId`: the ID to refer to the asset source. ``` engine.asset.removeSource(sourceId = source.sourceId) engine.asset.removeSource(sourceId = localSourceId) ``` ``` fun onAssetSourceAdded(): Flow ``` Subscribe to asset source addition events. * Returns flow of asset source addition events. ``` engine.asset.onAssetSourceAdded() .onEach { println("Asset source added: id=$it") } .launchIn(coroutineScope) ``` ``` fun onAssetSourceRemoved(): Flow ``` Subscribe to asset source removal events. * Returns flow of asset source removal events. ``` engine.asset.onAssetSourceRemoved() .onEach { println("Asset source removed: id=$it") } .launchIn(coroutineScope) ``` ``` fun onAssetSourceUpdated(): Flow ``` Subscribe to asset source's content update events. * Returns flow of asset source's content update events. ``` engine.asset.onAssetSourceUpdated() .onEach { println("Asset source updated: id=$it") } .launchIn(coroutineScope) ``` ## Finding and Applying Assets The `findAssets` function should return paginated asset results for the given `query`. The asset results have a set of mandatory and optional properties. For a listing with an explanation for each property please refer to the [Integrate a Custom Asset Source](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) guide. The properties of the `query` and the pagination mechanism are also explained in this guide. ``` suspend fun findAssets( sourceId: String, query: FindAssetsQuery, ): FindAssetsResult ``` Finds assets of a given type in a specific asset source. * `sourceId`: the ID of the asset source. * `query`: all the options to filter the search results by. * Returns the search results. ``` val list = engine.asset.findAssets( sourceId = source.sourceId, query = FindAssetsQuery(query = "", page = 1, perPage = 10) ) val sortByNewest = engine.asset.findAssets( sourceId = source.sourceId, query = FindAssetsQuery(query = "", page = 1, perPage = 10, sortingOrder = SortingOrder.DESCENDING) ) val sortById = engine.asset.findAssets( sourceId = source.sourceId, query = FindAssetsQuery(query = "", page = 1, perPage = 10, sortingOrder = SortingOrder.ASCENDING, sortKey = "id") ) val sortByMetaKeyValue = engine.asset.findAssets( sourceId = source.sourceId, query = FindAssetsQuery(query = "", page = 1, perPage = 10, sortingOrder = SortingOrder.ASCENDING, sortKey = "someMetaKey") ) val search = engine.asset.findAssets( sourceId = source.sourceId, query = FindAssetsQuery(query = "banana", page = 1, perPage = 10) ) ``` The optional function 'applyAssetSourceAsset' is to define the behavior of what to do when an asset gets applied to the scene. You can use the engine's APIs to do whatever you want with the given asset result. In this case, we always create an image block and add it to the first page we find. If you don't provide this function the engine's default behavior is to create a block based on the asset.meta\["blockType"\] property, add the block to the active page, and sensibly position and size it. ``` suspend fun applyAssetSourceAsset( sourceId: String, asset: Asset, ): DesignBlock? ``` Applies an asset to the active scene using custom `AssetSource.applyAsset` function. * `sourceId`: the sourceId of `AssetSource` which `AssetSource.applyAsset` function is going to be invoked. * `asset`: the asset to be applied to the active scene. Normally it's a single result of a `findAssets` query. * Returns the newly created block or null if no new block is created. ``` suspend fun applyAssetSourceAsset( sourceId: String, asset: Asset, block: DesignBlock, ) ``` Applies an asset to the `block` using custom `AssetSource.applyAsset` function. * `sourceId`: the sourceId of `AssetSource` which `AssetSource.applyAsset` function is going to be invoked. * `asset`: the asset that will be applied to the existing block. Normally it's a single result of a `findAssets` query. * `block`: the block that will be modified by the asset. ``` engine.asset.applyAssetSourceAsset(sourceId = source.sourceId, asset = list.assets[0]) engine.asset.applyAssetSourceAsset(sourceId = source.sourceId, asset = list.assets[0], block = block) ``` ``` suspend fun defaultApplyAsset(asset: Asset): DesignBlock? ``` This is the default implementation of applying asset to the active scene. This means a design block is instantiated and configured according to the `Asset.meta` properties. * `asset`: the asset to be applied to the active scene. Normally it's a single result of a `findAssets` query. * Returns the newly created block or null if no new block is created. ``` suspend fun defaultApplyAsset( asset: Asset, block: DesignBlock, ) ``` This is the default implementation of applying `asset` object to an existing `block`. This means it replaces the `block` content with `asset` content, if compatible. * `asset`: the asset that will be applied to the existing block. Normally it's a single result of a `findAssets` query. * `block`: the block that will be modified by the asset. ``` engine.asset.defaultApplyAsset(asset = search.assets[0]) engine.asset.defaultApplyAsset(asset = search.assets[0], block = block) ``` ``` fun getSourceSupportedMimeTypes(sourceId: String): List ``` Get the asset source's list of supported mime types. * `sourceId`: the ID of the asset source. * Returns the list of supported mime types of this asset source. ``` val mimeTypes = engine.asset.getSourceSupportedMimeTypes(sourceId = "ly.img.asset.source.unsplash") ``` ## Document Asset Sources A document color asset source is automatically available that allows listing all colors in the document. This asset source is read-only and is updated when `findAssets` is called. ``` val documentColors = engine.asset.findAssets( sourceId = "ly.img.scene.colors", query = FindAssetsQuery(query = "", page = 0, perPage = 99999) ) val colorAsset = documentColors.assets[0] ``` ## Add an Asset ``` fun addAsset( sourceId: String, asset: AssetDefinition, ) ``` Adds the given `asset` to a local asset source. * `sourceId`: The local asset source ID that the asset should be added to. * `asset`: the asset that should be added. ``` val assetDefinition = AssetDefinition( id = "localAssetId", meta = mapOf( "uri" to "https://example.com/localAssetId.jpg", "thumbUri" to "https://example.com/thumbnails/localAssetId.jpg", "kind" to "image", "fillType" to FillType.Image, "width" to "1080", "height" to "1920" ) ) engine.asset.addAsset(sourceId = localSourceId, asset = assetDefinition) ``` ## Remove an Asset ``` fun removeAsset( sourceId: String, assetId: String, ) ``` Removes the specified asset from its local asset source. * `sourceId`: the ID of the local asset source that currently contains the asset. * `assetId`: the asset id that should be removed. ``` engine.asset.removeAsset(sourceId = localSourceId, assetId = assetDefinition.id) ``` ## Asset Source Content Updates If the contents of your custom asset source change, you can call the `assetSourceContentsChanged` API to later notify all subscribers of the `onAssetSourceUpdated` API. ``` fun assetSourceContentsChanged(sourceId: String) ``` Notifies the engine that the contents of an asset source changed. * `sourceId`: the asset source whose contents changed. ``` engine.asset.assetSourceContentsChanged(sourceId = source.sourceId) ``` ## Groups in Assets ``` suspend fun getGroups(sourceId: String): List? ``` Queries the asset source's groups for a certain asset type. * `sourceId`: the ID of the asset source. * Returns the asset groups. ``` val groups = engine.asset.getGroups(sourceId = source.sourceId) ``` ## Credits and License ``` fun getCredits(sourceId: String): AssetCredits? ``` Queries the asset source's credits info. * `sourceId`: the ID of the asset source. * Returns the asset source's credits info consisting of a name and an optional URL. ``` val credits = engine.asset.getCredits(sourceId = source.sourceId) ``` ``` fun getLicense(sourceId: String): AssetLicense? ``` Queries the asset source's license info. * `sourceId`: the ID of the asset source. * Returns the asset source's license info consisting of a name and an optional URL. ``` val license = engine.asset.getLicense(sourceId = source.sourceId) ```